Membership Pause

Overview

This page documents subscription pause — a user choosing to skip billing for a set number of months while keeping their account active. This is distinct from account cancellation, which moves the user to PAUSED status. See User Lifecycle for cancellation.

A subscription pause lets an active user suspend their billing for 1–3 months. Their user status remains ACTIVE throughout. When the pause period ends, the Subscription Service fires a pause-resume event and billing resumes automatically.

MX cannot initiate a subscription pause. Only the user can request it. MX can only cancel a user’s membership (which sets user status to PAUSED).

Pause State Tracking

Two mechanisms track pause state in parallel:

Mechanism Role

membership_status on the Membership record

Source of truth for billing state. Consumed by the Subscription Service via the Kinesis Feeder.

MEMBERSHIP_PAUSE_STATUS tag on the User

Tracks the pause lifecycle for the user-service and subscription-worker. Updated as the pause progresses.

Pause State Lifecycle

(not paused)
     │ User requests pause
     ▼
SUB_PENDING_PAUSE  ← membership status + tag value
     │ Next billing date arrives, collection skipped (pause-skipped event)
     ▼
SUB_PAUSED  ← membership status + tag value
     │ Pause duration expires (pause-resume event) OR user manually unpauses
     ▼
SUB_RESUMED / (empty)  ← membership restored to good standing, tag archived

Pause Eligibility

A pause request is denied with 403 Forbidden in any of the following cases:

Condition Reason

User status is not ACTIVE

Inactive users cannot pause

User’s state is not in the Growthbook paused_states list

Pause is only available in certain US states

Requested duration exceeds 3 months

Maximum pause is maxPauseMonths = 3

PAUSE_SUB_DISABLED tag is present and not archived

User has been explicitly blocked from pausing

MEMBERSHIP_PAUSE_STATUS tag is SUB_PAUSED or SUB_PENDING_PAUSE

User is already paused

Pause Flow (User-Initiated)

POST /{user_id}/user/membership/pause

Request

{
  "pause_duration_months": 2
}

pause_duration_months must be between 1 and 3. If 0 or negative, it is stored as -1 (indefinite pause).

Flow

Decode request
     │
     ▼
Get user ──not found──► 404
     │
     ▼
Validate pause eligibility (status, state, duration) ──denied──► 403
     │
     ▼
Check pause tags (PAUSE_SUB_DISABLED, already paused) ──denied──► 403
     │
     ▼
Get next SCHEDULED subscription date (Subscription Service)
     │
     ▼
Get current membership record (or create default BASE record if none exists)
     │
     ▼
Upsert MEMBERSHIP_PAUSE_STATUS tag → SUB_PENDING_PAUSE (active)
     │
     ▼
Write new membership record:
  event_type:            SUB_PAUSED
  status:                SUB_PENDING_PAUSE
  pause_date:            next subscription date
  pause_duration_months: request value (or -1 if ≤ 0)
  unpause_date:          pause_date + N months (if duration > 0)
  (tier and term carried forward)
     │
     ▼
Send pause notification via Segment
     │
     ▼
Return 201 Created
  { "pause_date": "...", "unpause_date": "..." }

Admin Pause Flow

POST /users/{user_id}/admin/membership/pause

The admin pause endpoint applies the same eligibility checks as the user-initiated flow. The difference is the request format and the Segment notification sent.

Request

{
  "pause_duration_months": 1,
  "source": "MX"
}

source is stored as membership_event_source on the membership record.

Unpause Flow (User-Initiated)

POST /{user_id}/user/membership/unpause

No request body required.

Flow

Get user ──not found──► 404
     │
     ▼
User status is ACTIVE? ──No──► 403
     │ Yes
     ▼
Get user tags (active only)
     │
     ▼
MEMBERSHIP_PAUSE_STATUS tag exists? ──No──► 403 "user is not currently paused"
     │ Yes
     ▼
Get next PAUSED subscription date (Subscription Service)
     │
     ▼
Get current membership record
     │
     ▼
Check tag value:

  SUB_PAUSED (collection already skipped):
    ├── Upsert tag → SUB_PENDING_RESUME (archived)
    └── membership.event_type = UNPAUSE_CHARGE
        (Subscription Service will charge the user on next cycle)

  SUB_PENDING_PAUSE (not yet skipped a collection):
    ├── Upsert tag → SUB_RESUMED (archived)
    └── membership.event_type = UNPAUSE

  SUB_PENDING_RESUME or SUB_RESUMED:
    └── 403 "user is already unpaused"
     │
     ▼
Write new membership record:
  status:               (empty — good standing)
  pause_date:           cleared
  pause_duration_months: 0
  is_pending_downgrade: preserved from previous record
  downgrade_date:       preserved from previous record
     │
     ▼
Send unpause notification via Segment
     │
     ▼
Return 201 Created
When a user unpauses after a collection has already been skipped (SUB_PAUSED), the UNPAUSE_CHARGE event type signals the Subscription Service to collect payment on the next cycle. When unpausing before any collection was skipped (SUB_PENDING_PAUSE), no charge is needed.
The is_pending_downgrade and downgrade_date fields are explicitly preserved during unpause so that a scheduled downgrade is not lost.

Automatic Resume (Subscription Service)

When the pause duration expires naturally, the Subscription Service fires a pause-resume event on the prod-subscriptions Kinesis stream. The subscription worker handles this event — see Event Flows: Subscription Worker.