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 |
|---|---|
|
What the Subscription Service acts on, via the users stream |
|
What this service reads to decide eligibility and the unpause path |
| State | Membership status | Tag | Entered by |
|---|---|---|---|
Pending pause |
|
|
Pause or Admin pause |
Paused |
|
|
|
Pending resume |
(empty) |
|
Unpause after a collection was skipped |
Resumed |
(empty) |
|
Unpause before any skip, or |
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 |
|
User’s |
|
Requested duration exceeds 3 months |
|
|
|
|
|
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.
-
Load the user (
404 USER_NOT_FOUND) and apply the eligibility checks. -
Fetch the user’s next
SCHEDULEDcollection date from the Subscription Service (500); this is thepause_date. -
Write a new membership carrying the current tier, term and version (the pending-downgrade fields are not carried; see Membership Downgrades): event
SUB_PAUSED, statusSUB_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_dateplus that many months. -
Set the
MEMBERSHIP_PAUSE_STATUStag toSUB_PENDING_PAUSE(active). -
Send Segment
membership_paused(email, duration, unpause date);201withpause_dateandunpause_date. Metricuser.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.
-
Load the user (
404 USER_NOT_FOUND); a user who is notACTIVEis answered403 USER_NOT_ACTIVE. -
Read the non-archived tags; without a
MEMBERSHIP_PAUSE_STATUStag the user is403 NOT_PAUSED. -
Fetch the next
PAUSEDcollection date from the Subscription Service (500) and the current membership (500when none exists). -
Write a new membership with the tier, term and version carried over, an empty status, cleared pause fields, and
is_pending_downgrade/downgrade_datecarried forward:-
tag
SUB_PAUSED(a collection was already skipped): eventUNPAUSE_CHARGE, so the Subscription Service collects on the next cycle; tag set toSUB_PENDING_RESUME, archived. -
tag
SUB_PENDING_PAUSE(no collection skipped yet): eventUNPAUSE; tag set toSUB_RESUMED, archived. -
any other tag value:
403 ALREADY_UNPAUSED.
-
-
Send Segment
membership_manual_unpaused(email, next collection date);201with an empty body. Metricuser.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 |
|---|---|
|
A collection was skipped because the membership is paused. When the pause tag is |
|
The pause expired. A new membership is written with the tier, term and version carried over, an empty status, event |
Source: pkg/subscriptions/handler.go