Manage Subscriptions

A subscription binds one customer to one plan and is the object that actually gets billed. It issues an invoice every billing cycle, tracks its own status through that cycle, and posts a webhook every time that status changes.

This page covers creating a subscription, the status lifecycle end to end, what happens each billing cycle, and how to pause, resume, cancel or terminate one.

Two things must exist first

A plan to subscribe to, and a customer to subscribe. If you want cycles billed automatically, you also need a saved card token for that customer before the first invoice falls due.


The whole lifecycle, in motion

One subscription, from creation through a trial, a paid cycle, a missed cycle, and the end of the plan.


Create a subscription

POST /api/v1.4/subscriptions/
FieldRequiredWhat it does
planYesThe plan id this subscription is attached to.
customerYesThe customer UUID being subscribed.
start_dateYesStart of the trial, or of the first billing cycle when there is no trial.
charge_automaticallyNotrue bills the saved card at each due date. Requires primary_card_token. Defaults to false.
primary_card_tokenConditionalThe saved card token used for automatic charges. Required when charge_automatically is true.
customizationNoPer-subscription overrides of the plan's terms. See below.
webhook_urlNoWhere MoneyHash POSTs subscription status updates.
custom_fieldsNoKey/value pairs stored with the subscription and filterable on list.
flow_idNoThe payment flow used for this subscription's invoice payments.
curl -X POST https://web.moneyhash.io/api/v1.4/subscriptions/ \
  -H "x-api-key: <YOUR_ACCOUNT_API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "plan": "<YOUR_PLAN_ID>",
    "customer": "<YOUR_CUSTOMER_ID>",
    "start_date": "2026-10-01",
    "charge_automatically": true,
    "primary_card_token": "<YOUR_CARD_TOKEN_ID>",
    "webhook_url": "https://your-server.com/webhooks/subscriptions",
    "custom_fields": { "tier": "basic" }
  }'

The response is the full subscription object. The fields worth knowing:

{
  "status": { "code": 200, "message": "success", "errors": [] },
  "data": {
    "id": "<YOUR_SUBSCRIPTION_ID>",
    "account": { "id": "<YOUR_ACCOUNT_ID>", "name": "Demo Account" },
    "plan": { "id": "<YOUR_PLAN_ID>", "name": "Basic Monthly" },
    "customer": { "id": "<YOUR_CUSTOMER_ID>", "name": "Test Customer" },
    "current_status": { "id": "...", "value": "NEW", "created": "..." },
    "statuses": [ { "id": "...", "value": "NEW", "created": "..." } ],
    "start_date": "2026-10-01",
    "end_date": null,
    "charge_automatically": true,
    "remaining_recurring_cycles": null,
    "remaining_discount_cycles": null,
    "configuration": {
      "amount": 49,
      "one_time_fee": null,
      "trial_period": null,
      "recurrency": 1,
      "recurring_cycles": null,
      "discount_amount": null,
      "discount_percentage": null,
      "discount_cycles": null
    },
    "due_invoices": [],
    "next_recurring_cycle_invoice": {},
    "all_invoices": [],
    "transactions": [],
    "scheduled_updates": [],
    "flow": null,
    "webhook_url": "https://your-server.com/webhooks/subscriptions",
    "custom_fields": { "tier": "basic" },
    "is_live": false,
    "created": "2026-09-20T00:01:41.500439Z"
  },
  "count": 1, "next": null, "previous": null
}

configuration is the effective configuration for this subscription: the plan's terms with any customization already applied. Read it rather than the plan when you want to know what this customer is actually being charged.

statuses is the full history, current_status the head of it. scheduled_updates holds changes you have queued for the next cycle - see Update Subscriptions.


Automatic billing, or a link each cycle

charge_automatically is the single most consequential field on a subscription. It decides whether MoneyHash collects each cycle for you, or whether you have to.


charge_automatically: true - at each due date MoneyHash charges primary_card_token as a merchant-initiated transaction. The invoice is paid without the customer doing anything, so the subscription cycles inside ACTIVE and never drops to PAST_DUE.

charge_automatically: false - nothing is collected for you. Each cycle issues an invoice carrying a payment_intent_url, and it is on you to get that link to the customer. Until they pay it, the due date passes and the subscription moves to PAST_DUE.

"due_invoices": [
  {
    "id": "<INVOICE_ID>",
    "subscription_id": "<YOUR_SUBSCRIPTION_ID>",
    "status": { "id": "...", "value": "DUE", "created": "..." },
    "amount": 49,
    "currency": "SAR",
    "due_date": "2026-11-01",
    "payment_intent_url": "https://embed.moneyhash.io/embed/payment/<INTENT_ID>",
    "payment_data": { "intent_id": "<INTENT_ID>", "intent": { "...": "..." } }
  }
]

You can switch a manual subscription onto automatic billing later by storing a card token against it. See Update Subscriptions.


Customizing one subscription

customization overrides the plan's terms for this subscription only. The plan is untouched, and every other subscriber on it is unaffected. Use it for a negotiated price, a longer trial for one account, or a one-off discount.

OverrideEffect
amountCharge this customer a different amount per cycle.
one_time_feeAdd, change or remove the upfront fee.
trial_periodGive this customer a different trial length.
recurrencyChange the interval between cycles.
recurring_cyclesChange how many cycles this subscription runs for.
discount_amountA fixed discount, instead of the plan's.
discount_percentageA percentage discount, instead of the plan's.
discount_cyclesHow many cycles the discount applies to.
{
  "plan": "<YOUR_PLAN_ID>",
  "customer": "<YOUR_CUSTOMER_ID>",
  "start_date": "2026-10-01",
  "customization": {
    "amount": "39.00",
    "trial_period": 30,
    "discount_percentage": "10.00",
    "discount_cycles": 3
  }
}

The subscription lifecycle

A subscription starts at NEW and moves through the states below as time passes and invoices are paid or missed.

StatusWhat it meansMoves to
NEWThe default status of a new subscription, before its start_date.TRIAL if the plan has a trial period. INCOMPLETE if it does not, as it awaits the first payment.
TRIALThe subscription is in its trial period.INCOMPLETE if the trial ends and cycle one is unpaid. ACTIVE if the trial ends and cycle one is already paid. PENDING_CANCELLATION if cancelled during the trial. TERMINATED on termination.
INCOMPLETEThe first billing cycle has not been paid.ACTIVE once cycle one is paid. ENDED if the final recurring cycle completes. TERMINATED on termination.
ACTIVEThe customer is paying correctly. This is the steady state.ACTIVE again each time the next invoice is paid on or before the due date. PAST_DUE if a due date passes unpaid. PAUSED on pause. ENDED on the last recurring cycle. PENDING_CANCELLATION on cancellation. TERMINATED on termination.
PAST_DUEAn invoice is overdue. Access is still technically live, so this is your cue to chase or restrict.ACTIVE once the due invoice is paid. PAST_DUE again if another due date passes. ENDED on the last cycle. PENDING_CANCELLATION on cancellation. TERMINATED on termination.
PAUSEDBilling is temporarily suspended. No charges happen until it is resumed.ACTIVE on resume. ENDED on the last cycle. TERMINATED on termination.
ENDEDThe final recurring cycle completed.Final.
PENDING_CANCELLATIONCancellation is requested. The customer keeps access until the next billing cycle date.CANCELLED when that date arrives.
CANCELLEDThe cancellation took effect and access is revoked.Final.
TERMINATEDAccess revoked at the moment of the request. Reachable from any status except a cancellation already in progress.Final.

Ending a subscription

Three ways out, and they differ only in when the customer loses access.

  • Ended - let it run out. When the subscription reaches its last recurring cycle it ends on its own. A plan with an empty recurring_cycles never gets here.
  • Cancelled - cancel at any point in the cycle. The customer keeps access until the current billing cycle date is reached. The status sits at PENDING_CANCELLATION in the meantime, then flips to CANCELLED.
  • Terminated - stop now. Billing ceases and access is revoked from the moment of the request, whatever the current status.

Day-to-day operations

Beyond creating one, four operations manage a subscription's rhythm:

OperationWhat it does
PauseTemporarily suspends the billing cycle without cancelling. The subscription moves to PAUSED.
ResumeReinstates a paused subscription so billing continues. Back to ACTIVE.
CancelConcludes the subscription. Access persists until the end of the ongoing billing cycle.
TerminateImmediately ceases billing and revokes access.

For changing what a subscription costs or which plan it is on, see Update Subscriptions.

Retrying a failed automatic charge

When charge_automatically is true and the card is declined, you can re-attempt the outstanding invoices without waiting for the next cycle.

POST /api/v1.4/subscriptions/{subscription_id}/retry-payment/

This only works on subscriptions with charge_automatically: true. A manual subscription has no card to retry against, so send the customer the invoice's payment_intent_url instead.


Subscription invoices

Every billing cycle creates one invoice. It carries its own status, independent of the subscription's.

StatusWhat it meansMoves to
NEWThe default status of a new invoice.OPEN if the due date has not passed. DUE if it has.
OPENThe due date is still ahead and the customer has not paid.DUE when the due date passes. PAID when paid. CANCELLED if cancelled.
DUEThe due date has passed and the invoice is unpaid.PAID when paid. CANCELLED if cancelled.
PAIDThe invoice has been paid.Final.
CANCELLEDThe invoice was cancelled.Final.

The subscription object exposes invoices in three places: due_invoices (outstanding), next_recurring_cycle_invoice (the one coming), and all_invoices (the history).


Reading subscriptions back

GET /api/v1.4/subscriptions/{subscription_id}/
GET /api/v1.4/subscriptions/
GET /api/v1.4/customers/{customer_uuid}/subscriptions/

The list endpoint pages with limit and offset, and filters on the custom fields you stored at creation:

curl "https://web.moneyhash.io/api/v1.4/subscriptions/?custom_fields_name=tier&custom_fields_value=basic&limit=50" \
  -H "x-api-key: <YOUR_ACCOUNT_API_KEY>"

Webhooks

Every subscription status change posts a webhook. The payload carries "type": "subscription" and the event type is subscription.<STATUS> - for example subscription.ACTIVE or subscription.PAST_DUE.

Set the destination per subscription with webhook_url at creation. The body is the same subscription object returned by the API, so current_status.value is what you switch on, and statuses gives you the whole history if you missed a delivery.

Webhooks are how you learn about renewals

Nothing about a renewal, a missed payment, or a cancellation taking effect is returned synchronously - those all happen on a schedule, long after your API call returned. Configure webhook_url and handle every status in the table above, or your side of the integration will drift out of sync with MoneyHash.


Testing the lifecycle without waiting

Waiting a real month to see a renewal is not a test strategy. MoneyHash ships pre-configured scenarios you can drive from the Simulate subscription endpoint or from the Sandbox.

Replace the simulation_subscription parameter with the subscription id you want to simulate, and send a command in the body:

commandWhat it simulates
jump_to_the_next_cycle_start_dateAdvances the subscription to the next cycle's start date, so you can exercise renewal without waiting for the real due date.
pay_all_issued_invoicesMarks every invoice issued so far as paid, simulating a customer who is fully up to date.

Between them you can walk a subscription through INCOMPLETE, ACTIVE, PAST_DUE and ENDED in minutes, and confirm your webhook handler reacts correctly to each one.


Where to next


Did this page help you?