Membership Pause

A subscription pause lets an ACTIVE user skip billing for one to three months (indefinitely when set by MX) while keeping their account open; the user status stays ACTIVE throughout. It is distinct from cancellation, which moves the user to PAUSED. Error bodies are the MembershipPauseError schema in the API Specification.

Pause State

Two records track the pause in parallel:

Record Role

membership_status on the Membership

What the Subscription Service acts on, via the users stream

MEMBERSHIP_PAUSE_STATUS tag

What this service reads to decide eligibility and the unpause path

State Membership status Tag Entered by

Pending pause

SUB_PENDING_PAUSE

SUB_PENDING_PAUSE (active)

Pause or Admin pause

Paused

SUB_PAUSED

SUB_PAUSED (active)

pause-skipped: the first collection inside the pause is skipped

Pending resume

(empty)

SUB_PENDING_RESUME (archived)

Unpause after a collection was skipped

Resumed

(empty)

SUB_RESUMED (archived)

Unpause before any skip, or pause-resume

Eligibility

Checked in this order by the pause and admin-pause operations; a failure is 403 with the code shown.

Condition error_code

User status is not ACTIVE

USER_NOT_ACTIVE

User’s state is not in the GrowthBook paused_states list (Feature Flags)

STATE_NOT_ELIGIBLE

Requested duration exceeds 3 months

PAUSE_DURATION_TOO_LONG

PAUSE_SUB_DISABLED tag present and not archived

PAUSE_BLOCKED

MEMBERSHIP_PAUSE_STATUS tag is SUB_PAUSED or SUB_PENDING_PAUSE

ALREADY_PAUSED

A tag of SUB_PENDING_RESUME is allowed but emits user.membership.pauseFraud — Pause fraud signal.

Source: pkg/api/membership_pausing.go

Pause

POST /{user_id}/user/membership/pause with pause_duration_months.

  1. Load the user (404 USER_NOT_FOUND) and apply the eligibility checks.

  2. Fetch the user’s next SCHEDULED collection date from the Subscription Service (500); this is the pause_date.

  3. Write a new membership carrying the current tier, term and version (the pending-downgrade fields are not carried; see Membership Downgrades): event SUB_PAUSED, status SUB_PENDING_PAUSE, pause_date, pause_duration_months (a value of 0 or less is stored as -1, indefinite) and, for a positive duration, unpause_date = pause_date plus that many months.

  4. Set the MEMBERSHIP_PAUSE_STATUS tag to SUB_PENDING_PAUSE (active).

  5. Send Segment membership_paused (email, duration, unpause date); 201 with pause_date and unpause_date. Metric user.membership.paused.

Admin Pause

POST /users/{user_id}/admin/membership/pause (Admin API) with an optional pause_duration_months and a source stored as the membership event source. A missing duration pauses indefinitely (-1). The checks and record writes are the same as Pause; the notification is Segment membership_paused_plaid and the metric carries admin:true.

Unpause

POST /{user_id}/user/membership/unpause, no body.

  1. Load the user (404 USER_NOT_FOUND); a user who is not ACTIVE is answered 403 USER_NOT_ACTIVE.

  2. Read the non-archived tags; without a MEMBERSHIP_PAUSE_STATUS tag the user is 403 NOT_PAUSED.

  3. Fetch the next PAUSED collection date from the Subscription Service (500) and the current membership (500 when none exists).

  4. Write a new membership with the tier, term and version carried over, an empty status, cleared pause fields, and is_pending_downgrade / downgrade_date carried forward:

    • tag SUB_PAUSED (a collection was already skipped): event UNPAUSE_CHARGE, so the Subscription Service collects on the next cycle; tag set to SUB_PENDING_RESUME, archived.

    • tag SUB_PENDING_PAUSE (no collection skipped yet): event UNPAUSE; tag set to SUB_RESUMED, archived.

    • any other tag value: 403 ALREADY_UNPAUSED.

  5. Send Segment membership_manual_unpaused (email, next collection date); 201 with an empty body. Metric user.membership.unpaused.

The USER_NOT_ACTIVE check does not stop processing: steps 2–5 still run for an inactive user and their side effects (membership record, tag, notification) are persisted before the 403 is returned. TestUnpauseMembershipInactiveUserStillProcessed pins this behaviour.

Subscription Worker

The subscriptions Lambda consumes {env}-subscriptions (filter and batch settings in Infrastructure). Records in a batch are processed concurrently; any failure fails the batch, which Kinesis bisects and retries. payment-failed passes the Terraform filter and is logged as unsupported. cancel-successful and account-closed record a settlement participant’s cancellation (Settlement participants).

Event Action

pause-skipped

A collection was skipped because the membership is paused. When the pause tag is SUB_PENDING_PAUSE it becomes SUB_PAUSED (active). When the membership status is not yet SUB_PAUSED, the current record is re-written with status SUB_PAUSED and event PAUSE_COLLECTION_SKIPPED, an event type the Subscription Service does not act on.

pause-resume

The pause expired. A new membership is written with the tier, term and version carried over, an empty status, event SUB_RESUMED and cleared pause fields; the pause tag becomes SUB_RESUMED, archived; Segment membership_unpaused (email, today’s date) is sent and user.membership.unpaused emitted.

Source: pkg/subscriptions/handler.go