Billing

Subscriptions

Recurring billing for any product whose price has type recurring. Created the same way as a one-time checkout. Merida figures out the cadence from the price and starts invoicing on the schedule.

Create a subscription checkout

Subscriptions are minted via the same POST /checkouts call as one-off payments. Pass a product_id whose active price has type: "recurring" and an interval ("day", "week", "month", "year"). The buyer lands on the hosted checkout, pays the first cycle, and a recurring schedule starts.

bash
curl -X POST https://api.meridapay.com/checkouts \
  -H "Authorization: Bearer $MERIDA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "product_id": "<recurring-product-uuid>",
    "customer_email": "[email protected]",
    "external_customer_id": "user_123",
    "metadata": { "plan": "pro" }
  }'

The metadata you set here lives on every subsequent invoice and webhook event for the lifetime of the subscription. Use it to carry your internal plan ID or any correlation key.

Lifecycle events

Webhook events tell the story of every subscription. Subscribe to all of them or pick a subset.

subscription.createdwebhook

Fires once when a subscription is created (checkout success, manual create, or trial start).

subscription.updatedwebhook

Fires on plan change, pause, resume, or scheduled cancel. The payload includes the new state and the effective timestamp.

subscription.canceledwebhook

Fires when a subscription is canceled (immediately or at period end).

subscription.expiredwebhook

Fires when a subscription reaches its end and is no longer active.

subscription.trial_endedwebhook

Fires when a trial completes, either converted into a paid subscription or expired.

invoice.createdwebhook

Fires whenever a new invoice is generated: at renewal, ad-hoc billing, or proration on plan change. Use the invoice ID to fetch the PDF or render line items in your own UI.

payment.succeededwebhook

Fires once the renewal charge clears. Carries paymentLinkId, subscriptionId (when the source is a renewal), and the same metadata you set on creation.

payment.failedwebhook

Fires when a renewal charge is declined. Surface a retry prompt to the customer or fall back to dunning.

Plan changes

Plan changes are dashboard-driven today. The merchant moves a customer between products in the Merida UI, which mints a proration invoice and emits subscription.updated. There's no public REST endpoint for self-service plan changes yet. Surface upgrades in your own product UI by linking the customer to a new checkout for the higher-tier product.

Cancellation and pausing

The end-customer manages their own subscription via the self-service portal. You hand them a tokenised URL (/portal/<token>). They click cancel, pause, or resume, and Merida fires subscription.canceled (cancel) or subscription.updated (pause/resume) so your backend stays in sync.

Two cancellation modes:

  • period_end (default): keeps the subscription active until the end of the paid period, then doesn't renew. The customer keeps access.
  • immediate: terminates now and refunds nothing automatically. Use when a customer churns mid-cycle and you handle credit on your side.

Pause keeps the subscription billable but skips the next renewal until resumes_at. Resume undoes the pause. Pausing is a soft alternative to cancellation: the metadata, plan, and external IDs all carry through.

See the portal endpoints for the exact REST shape.

Correlating to your user

Every subscription event includes externalCustomerId and the full metadata map you set at checkout creation. Prefer those over the raw subscriptionId when wiring up entitlement gates. They survive even if the customer churns and re-subscribes with a fresh subscription record.