Money In

Subscriptions

 

Create and manage recurring card subscriptions on a customer's vaulted cards. Line items come from your product catalog — each product's pricing type and billing interval determine what is billed each cycle and on the first cycle only. Supports trial periods, automatic charging with card cascade, plan changes, pausing, and cancellation — with invoice issuance and webhook notifications.

Create Request

Request to create a subscription that bills a customer on a recurring schedule. On each billing date the customer's vaulted card(s) are charged automatically — the default card is attempted first, cascading through the remaining active cards in priority order (see Card Vault Charge). Pass card_id to bill a specific card only (no cascade).

Recurring charges are processed as merchant-initiated transactions (MIT) — no 3DS challenge is presented. The 3DS authentication from the original card storage (see Card Vault Link) provides the authentication reference for scheme compliance. The customer must therefore have at least one ACTIVE vaulted card, unless collection_method is REQUEST_PAYMENT.

Line items come from your product catalog — see products. Pass a single items array; each product's pricing type determines how it is stored and billed: RECURRING products bill on every cycle, while ONE_TIME products (e.g. a setup fee) are billed on the first cycle only.

The billing schedule is determined by the products. Each recurring product carries its own billing interval (e.g. monthly, yearly, or a custom interval such as every 6 weeks), which populates the subscription's billing.period — and billing.custom_interval / billing.custom_period for custom intervals. You do not pass a period on the request; all RECURRING products in one subscription must share the same billing interval, otherwise the request is rejected.

Path

POST /subscriptions/{customer_id}

Path ParameterTypeDescriptionExample
customer_idString(32)The customer to subscribe — see customerscus_abc123...

Example (Basic)

Basic example of a subscription starting today. The billing period comes from the product — a monthly product bills monthly. The customer's default card is charged on each billing date, cascading through fallback cards on decline.

{
  "items": [
    {
      "product_id": "pro_abc123...", // RECURRING catalog product; its interval sets the billing period
      "qty": 1 // default 1 when omitted
    }
  ],
  "reference": "SUB-2026-00042" // your reference; appears on statements and reporting
}

Example (Trial and setup fee)

Start with a 14-day trial. The ONE_TIME setup fee product is automatically billed on the first cycle only, together with the first recurring cycle when the trial ends.

{
  "items": [
    {
      "product_id": "pro_abc123...", // RECURRING — billed every cycle
      "qty": 1
    },
    {
      "product_id": "pro_setup1..." // ONE_TIME — billed on the first cycle only
    }
  ],
  "trial_days": 14, // subscription starts in TRIAL; first charge when the trial ends
  "reference": "SUB-2026-00042"
}

Example (Advanced)

Advanced example with all options, including billing anchoring, invoicing, cascade controls, and a fixed end date.

{
  "items": [
    {
      "product_id": "pro_abc123...",
      "qty": 2 // default 1 when omitted
    },
    {
      "product_id": "pro_xyz789...",
      "price_excl": 300.23 // override the default product amount
    },
    {
      "product_id": "pro_setup1..." // ONE_TIME product — billed on the first cycle only
    }
  ],

  "currency": "ZAR", // [optional] default "ZAR"

  "reference": "SUB-2026-00042", // your reference; appears on statements and reporting

  "signature": "secret-key-for-payload", // [optional] shared-secret signature for the payload, see the signature section below

  // Billing schedule — the period itself comes from the recurring product's interval and cannot be set here
  "billing": {
    "billing_day": 1, // [optional] anchor day of the month (1–31) for MONTHLY and longer periods; defaults to the day of date_start
    "date_start": "2026-08-01", // [optional] default today; first billing date (after any trial)
    "date_end": "2027-07-31" // [optional] subscription expires after this date; omit to run until cancelled
  },

  "trial_days": 14, // [optional] trial period before the first charge

  "collection_method": "AUTOCHARGE", // [optional] default AUTOCHARGE — charge the vaulted card(s); REQUEST_PAYMENT sends the customer a payment request instead
  "due_days": 7, // [optional] REQUEST_PAYMENT only — days after each billing date before the payment request is overdue

  "card_id": "crd_SFq2E9LskQimPkf2mRqnV", // [optional] bill this card only; the cascade is disabled. Omit to use the default card with cascade

  // Cascade behaviour on each billing date — ignored when card_id is passed
  "cascade": {
    "is_enabled": true, // [optional] default true — when false, only the default card is attempted
    "max_attempts": 3, // [optional] cap on the number of cards tried; defaults to all ACTIVE cards
    "skip_codes": ["DO_NOT_HONOUR"] // [optional] additional decline codes that abort the cascade; hard declines (e.g. SUSPECTED_FRAUD, STOLEN_CARD, PICKUP_CARD) always abort and cannot be overridden
  },

  "is_tax": true, // [optional] default true — apply tax to line items

  // Issue and optionally email an invoice on each billing cycle
  "invoice": {
    "is_generate": true,
    "is_send": true, // email each invoice to the customer when true
    "memo": "Thank you for subscribing", // [optional] shown above the invoice lines
    "footer": "VAT No. 4123456789" // [optional] shown below the invoice lines
  },

  "notification": {
    "email": "me@my-email.co.za", // [optional] email notification on subscription lifecycle events
    "webhook_url": "https://merchant.example/webhooks/subscriptions" // webhook on subscription lifecycle events
  },

  "metadata": {
    "order_id": "ord_98765" // [optional] custom key/value pairs returned on the subscription and webhooks
  }
}

Request Parameters

Fields below appear in the request body examples on this page. Y = required for all requests, C = required or applicable depending on usage.

Field
RequiredType
Description
Example
itemsYArrayLine items from your catalog. Each product's pricing type determines how it is billed: RECURRING products bill on every cycle, ONE_TIME products on the first cycle only. At least one RECURRING product is required, and all RECURRING products must share the same billing interval — see productsSee basic example
items.product_idYStringCatalog product for this linepro_abc123...
items.qtyNIntegerQuantity for this line item (default 1)2
items.price_exclNNumberOverride the default product amount (excluding tax)300.23
billing.billing_dayNIntegerAnchor day of the month (131) for MONTHLY and longer periods; months with fewer days bill on the last day. Defaults to the day of date_start. The billing period itself is derived from the recurring product's interval and cannot be set here1
billing.date_startNStringFirst billing date (YYYY-MM-DD), after any trial. Default today2026-08-01
billing.date_endNStringDate after which the subscription expires with status EXPIRED. Omit to run until cancelled2027-07-31
trial_daysNIntegerTrial period in days before the first charge; the subscription starts in TRIAL14
collection_methodNENUMAUTOCHARGE (default) charges the vaulted card(s) on each billing date; REQUEST_PAYMENT emails the customer a payment request instead, see lookupsAUTOCHARGE
due_daysCIntegerREQUEST_PAYMENT only — days after each billing date before the payment request is overdue7
card_idCString(32)Bill this specific card only; the cascade is disabled. Omit to bill the default card with cascadecrd_SFq2E9LskQimPkf2mRqnV
cascadeNObjectCascade behaviour on each billing date; ignored when card_id is passed — see Card Vault ChargeSee advanced example
cascade.is_enabledNBooleanWhen false, only the default card is attempted. Default truetrue
cascade.max_attemptsNIntegerCap on the number of cards tried; defaults to all ACTIVE cards3
cascade.skip_codesNArrayAdditional decline codes that abort the cascade instead of trying the next card. Hard declines (SUSPECTED_FRAUD, STOLEN_CARD, PICKUP_CARD) always abort and cannot be overridden — see lookups[DO_NOT_HONOUR]
currencyNString(3)ISO 4217 currency code. Default ZARZAR
referenceYString(35)Your reference for this subscription; appears on statements and reportingSUB-2026-00042
is_taxNBooleanApply tax to line items. Default truetrue
signatureNStringOptional passphrase signature for verifying the payloadsecret-key-for-payload
invoice.is_generateNBooleanWhether to generate an invoice on each billing cycletrue
invoice.is_sendNBooleanWhether to email each invoice to the customertrue
invoice.memoNStringMemo shown above the invoice linesThank you for subscribing
invoice.footerNStringFooter shown below the invoice linesVAT No. 4123456789
notification.emailNStringEmail address to notify on subscription lifecycle eventsme@my-email.co.za
notification.webhook_urlNStringWebhook URL for subscription lifecycle eventshttps://merchant.example/webhooks/subscriptions
metadataNObjectCustom key/value metadata returned on the subscription and webhooks{"order_id": "ord_98765"}

Response Body

A subscription (sub_) is returned with its resolved billing schedule and totals. The items array you passed is split by each product's pricing type: RECURRING products are stored under items and ONE_TIME products under items_one_time. The recurring product's interval populates billing.period — and billing.custom_interval / billing.custom_period when the product bills on a custom interval.

Each billing cycle produces a charge on the customer's card(s) — the latest outcome is reflected in last_transaction_id and last_invoice_id.

{
    "status": true,
    "result": {
      "id": "sub_Wt5RmK2xPqLv9nYcE7aBz",
      "customer_id": "cus_abc123...",
      "reference": "SUB-2026-00042",
      "subscription_status": "TRIAL",
      "collection_method": "AUTOCHARGE",
      "card_id": null,
      "items": [
        {
          "product_id": "pro_abc123...",
          "qty": 2,
          "price_excl": 300.23
        }
      ],
      "items_one_time": [
        {
          "product_id": "pro_setup1...",
          "qty": 1
        }
      ],
      "billing": {
        "period": "MONTHLY",
        "billing_day": 1,
        "date_start": "2026-08-01",
        "date_end": "2027-07-31"
      },
      "trial_days": 14,
      "total_recurring_excl": "600.46",
      "total_recurring_incl": "690.53",
      "total_one_time_excl": "150.00",
      "total_one_time_incl": "172.50",
      "currency": "ZAR",
      "date_next_billing": "2026-08-01",
      "date_last_billing": null,
      "last_transaction_id": null,
      "last_invoice_id": null,
      "created_at": "2026-07-06T10:15:30Z"
    }
}

Response Parameters

Field
Type
Description
Example
statusBooleanWhether the request succeededtrue
result.idString(32)Unique subscription identifiersub_Wt5RmK2xPqLv9nYcE7aBz
result.customer_idString(32)Associated customer IDcus_abc123...
result.referenceString(35)Your reference for this subscriptionSUB-2026-00042
result.subscription_statusENUMSubscription state: TRIAL, ACTIVE, PAUSED, CANCELLED, EXPIRED, FAILED, or DISABLED, see lookupsTRIAL
result.collection_methodENUMHow each cycle is collected, see lookupsAUTOCHARGE
result.card_idString(32)The specific card billed each cycle; null when using the default card with cascadecrd_SFq2E9LskQimPkf2mRqnV
result.itemsArrayRECURRING line items, billed on every cycle...
result.items_one_timeArrayONE_TIME line items, billed on the first cycle only...
result.billingObjectResolved billing schedule. period is populated from the recurring product's interval; custom_interval and custom_period are included when the product bills on a custom interval, see lookupsSee response body
result.trial_daysIntegerTrial period in days; null when no trial14
result.total_recurring_exclStringRecurring total per cycle, excluding tax600.46
result.total_recurring_inclStringRecurring total per cycle, including tax690.53
result.total_one_time_exclStringOne-time total billed on the first cycle, excluding tax150.00
result.total_one_time_inclStringOne-time total billed on the first cycle, including tax172.50
result.currencyString(3)ISO 4217 currency codeZAR
result.date_next_billingStringNext billing date2026-08-01
result.date_last_billingStringMost recent billing date; null before the first cycle2026-08-01
result.last_transaction_idString(32)Transaction from the most recent billing cycle; null before the first cycletra_pr6CvR_4pvWwmgQ4y3dtY
result.last_invoice_idString(32)Invoice from the most recent billing cycle; null before the first cycle or when invoicing is disabledinv_k2Jd8sLqPz...
result.created_atStringISO timestamp when the subscription was created2026-07-06T10:15:30Z

Change Plan Request

Change the recurring line items of a subscription. As on create, the billing period is determined by the products — passing products with a different interval (e.g. moving from monthly to yearly) updates the subscription's billing.period when the plan change takes effect. ONE_TIME products are not allowed here; they are only billed on the first cycle of a new subscription.

By default the new plan takes effect on the next billing date; pass is_immediate to bill the new plan right away and reset the billing anchor to today. The pending plan is visible on the subscription as change_plan_items and change_plan_at until it takes effect.

Path

POST /subscriptions/{subscription_id}/change-plan

Path ParameterTypeDescriptionExample
subscription_idString(32)The subscription to changesub_Wt5RmK2xPqLv9nYcE7aBz

Example

{
  "items": [
    {
      "product_id": "pro_premium1...", // replaces the current recurring lines
      "qty": 1
    }
  ],
  "is_immediate": false, // [optional] default false — apply on the next billing date; true bills the new plan now and resets the billing anchor

  "signature": "secret-key-for-payload" // [optional] shared-secret signature for the payload, see the signature section below
}

Request Parameters

Field
RequiredType
Description
Example
itemsYArrayNew RECURRING line items; replaces the current lines and re-derives the billing period from the products — same shape as on createSee example
is_immediateNBooleanBill the new plan immediately and reset the billing anchor to today. Default false — the new plan takes effect on the next billing datefalse
signatureNStringOptional passphrase signature for verifying the payloadsecret-key-for-payload

Response Body

{
    "status": true,
    "result": {
      "id": "sub_Wt5RmK2xPqLv9nYcE7aBz",
      "customer_id": "cus_abc123...",
      "subscription_status": "ACTIVE",
      "change_plan_at": "2026-09-01T00:00:00Z",
      "change_plan_items": [
        {
          "product_id": "pro_premium1...",
          "qty": 1
        }
      ],
      "date_next_billing": "2026-09-01"
    }
}

Response Parameters

Field
Type
Description
Example
statusBooleanWhether the plan change was scheduled or appliedtrue
result.idString(32)The subscription the plan change belongs tosub_Wt5RmK2xPqLv9nYcE7aBz
result.customer_idString(32)Associated customer IDcus_abc123...
result.subscription_statusENUMSubscription state, see lookupsACTIVE
result.change_plan_atStringISO timestamp when the new plan takes effect; null once applied2026-09-01T00:00:00Z
result.change_plan_itemsArrayThe pending recurring line items; null once applied...
result.date_next_billingStringNext billing date2026-09-01

Pause Request

Pause an ACTIVE or TRIAL subscription. No charges occur while paused and the billing schedule is suspended. Resume at any time with the resume request.

Path

POST /subscriptions/{subscription_id}/pause

Path ParameterTypeDescriptionExample
subscription_idString(32)The subscription to pausesub_Wt5RmK2xPqLv9nYcE7aBz

Example

An empty body (or {}) is valid.

{
  "signature": "secret-key-for-payload" // [optional] shared-secret signature for the payload, see the signature section below
}

Response Body

{
    "status": true,
    "result": {
      "id": "sub_Wt5RmK2xPqLv9nYcE7aBz",
      "customer_id": "cus_abc123...",
      "subscription_status": "PAUSED",
      "date_next_billing": null,
      "paused_at": "2026-08-15T09:20:00Z"
    }
}

Resume Request

Resume a PAUSED subscription. Billing restarts on the next scheduled date; pass is_bill_now to charge the current cycle immediately and reset the billing anchor to today.

Path

POST /subscriptions/{subscription_id}/resume

Path ParameterTypeDescriptionExample
subscription_idString(32)The subscription to resumesub_Wt5RmK2xPqLv9nYcE7aBz

Example

An empty body (or {}) is valid.

{
  "is_bill_now": false, // [optional] default false — resume on the next scheduled billing date; true charges the current cycle now and resets the billing anchor
  "signature": "secret-key-for-payload" // [optional] shared-secret signature for the payload, see the signature section below
}

Response Body

{
    "status": true,
    "result": {
      "id": "sub_Wt5RmK2xPqLv9nYcE7aBz",
      "customer_id": "cus_abc123...",
      "subscription_status": "ACTIVE",
      "date_next_billing": "2026-09-01",
      "resumed_at": "2026-08-20T09:20:00Z"
    }
}

Cancel Request

Cancel a subscription. By default the cancellation takes effect at the end of the current billing period — the customer keeps access until the next billing date and no further charges occur. Pass is_immediate to cancel right away. Cancellation is permanent; create a new subscription to bill the customer again.

Path

POST /subscriptions/{subscription_id}/cancel

Path ParameterTypeDescriptionExample
subscription_idString(32)The subscription to cancelsub_Wt5RmK2xPqLv9nYcE7aBz

Example

An empty body (or {}) is valid.

{
  "is_immediate": false, // [optional] default false — cancel at the end of the current billing period
  "reason": "Customer downgraded", // [optional] recorded against the cancellation for reporting

  "signature": "secret-key-for-payload" // [optional] shared-secret signature for the payload, see the signature section below
}

Request Parameters

Field
RequiredType
Description
Example
is_immediateNBooleanCancel immediately instead of at the end of the current billing period. Default falsefalse
reasonNStringOptional reason recorded against the cancellation for reportingCustomer downgraded
signatureNStringOptional passphrase signature for verifying the payloadsecret-key-for-payload

Response Body

{
    "status": true,
    "result": {
      "id": "sub_Wt5RmK2xPqLv9nYcE7aBz",
      "customer_id": "cus_abc123...",
      "subscription_status": "ACTIVE",
      "date_cancel": "2026-09-01",
      "cancel_reason": "Customer downgraded",
      "date_next_billing": null
    }
}

Response Parameters

Field
Type
Description
Example
statusBooleanWhether the cancellation was scheduled or appliedtrue
result.idString(32)The subscription the cancellation belongs tosub_Wt5RmK2xPqLv9nYcE7aBz
result.customer_idString(32)Associated customer IDcus_abc123...
result.subscription_statusENUMRemains ACTIVE until the scheduled cancellation date, then CANCELLED; CANCELLED immediately when is_immediate is true, see lookupsACTIVE
result.date_cancelStringDate the cancellation takes effect2026-09-01
result.cancel_reasonStringReason recorded against the cancellationCustomer downgraded
result.date_next_billingStringnull — no further billing occursnull

Failed payments

When an AUTOCHARGE billing cycle declines on all cards, the charge is retried daily for up to 3 days. Each attempt fires subscription.charge_failed. If all retries are exhausted, the subscription moves to FAILED and billing stops until a successful payment is collected — for example after the customer stores a new card via a Card Vault Link, the next retry or a manual Card Vault Charge restores the subscription to ACTIVE.

Signature creation

When creating API keys on the dashboard you can download a passphrase key, use it to generate your signature and send it in the signature parameter. The canonicalization and HMAC-SHA256 process is identical across all endpoints — see Checkout Link — Signature creation for Node.js, PHP, C#, Java, and Python examples.

Webhook

Webhooks are delivered to notification.webhook_url for each subscription lifecycle event when that field was supplied on create. Broader platform webhooks are configured separately if applicable. Billing-cycle webhooks fire once per cycle outcome, not per cascade attempt.

Possible event values include subscription.created, subscription.charged, subscription.charge_failed, and others listed under Webhook events.

Webhook Payload

{
  // subscription.created · subscription.activated · subscription.charged · subscription.charge_failed ·
  // subscription.plan_changed · subscription.paused · subscription.resumed · subscription.cancelled ·
  // subscription.expired · subscription.failed
  "event": "subscription.charged",
  "data": [
    {
      "subscription": {
        "id": "sub_Wt5RmK2xPqLv9nYcE7aBz",
        "customer_id": "cus_abc123...",
        "reference": "SUB-2026-00042",
        "subscription_status": "ACTIVE",
        "collection_method": "AUTOCHARGE",
        "total_recurring_excl": "600.46",
        "total_recurring_incl": "690.53",
        "currency": "ZAR",
        "billing": {
          "period": "MONTHLY",
          "billing_day": 1,
          "date_start": "2026-08-01",
          "date_end": "2027-07-31"
        },
        "date_last_billing": "2026-08-01", // present from the first billing cycle
        "date_next_billing": "2026-09-01",
        "card_id": "crd_9kLmXw3RtYqPz8vNcE2aB", // card charged for this cycle; present on subscription.charged
        "transaction_id": "tra_pr6CvR_4pvWwmgQ4y3dtY", // present on subscription.charged
        "invoice_id": "inv_k2Jd8sLqPz...", // present when invoice.is_generate was true
        "error": null, // decline details; present on subscription.charge_failed
        "metadata": {
          "order_id": "ord_98765"
        },
        "completed_at": "2026-08-01T06:00:00Z"
      }
    }
  ],
  "created_at": "2026-08-01T06:00:00Z"
}

Webhook Payload Parameters

Field
Type
Description
Example
eventStringType of webhook event that occurredsubscription.charged
dataArrayArray containing subscription data...
data.subscription.idString(32)Unique subscription identifiersub_Wt5RmK2xPqLv9nYcE7aBz
data.subscription.customer_idString(32)Associated customer IDcus_abc123...
data.subscription.referenceString(35)Your reference for this subscriptionSUB-2026-00042
data.subscription.subscription_statusENUMSubscription state after the event, see lookupsACTIVE
data.subscription.collection_methodENUMHow each cycle is collected, see lookupsAUTOCHARGE
data.subscription.total_recurring_exclStringRecurring total per cycle, excluding tax600.46
data.subscription.total_recurring_inclStringRecurring total per cycle, including tax690.53
data.subscription.currencyString(3)ISO 4217 currency codeZAR
data.subscription.billingObjectResolved billing scheduleSee webhook payload
data.subscription.date_last_billingStringMost recent billing date2026-08-01
data.subscription.date_next_billingStringNext billing date; null when no further billing occurs2026-09-01
data.subscription.card_idString(32)Card charged for this cycle; present on subscription.chargedcrd_9kLmXw3RtYqPz8vNcE2aB
data.subscription.transaction_idString(32)Transaction for this cycle; present on subscription.chargedtra_pr6CvR_4pvWwmgQ4y3dtY
data.subscription.invoice_idString(32)Invoice for this cycle when invoice.is_generate was trueinv_k2Jd8sLqPz...
data.subscription.error.codeENUMDecline code; present on subscription.charge_failed, see lookupsINSUFFICIENT_FUNDS
data.subscription.error.messageStringHuman-readable decline reason; present on subscription.charge_failedThe transaction was declined due to insufficient funds
data.subscription.metadataObjectCustom key/value metadata supplied on create{"order_id": "ord_98765"}
data.subscription.completed_atStringISO timestamp when the subscription reached the status for this event2026-08-01T06:00:00Z
created_atStringISO timestamp when webhook was created2026-08-01T06:00:00Z

Webhook Events

EventDescriptionTrigger ConditionData Included
subscription.createdThe subscription was createdWhen the create request succeedsSubscription details, billing schedule, totals
subscription.activatedThe subscription became activeWhen the trial ends or the first charge succeedsSubscription details, next billing date
subscription.chargedA billing cycle was collected successfullyWhen the cycle charge succeeds on any card in the cascadeSubscription details, charged card, transaction, invoice when generated
subscription.charge_failedA billing cycle charge attempt failedWhen a cycle charge declines on all attempted cards; fires once per retrySubscription details, decline code and message
subscription.plan_changedThe recurring line items changedWhen a scheduled or immediate plan change takes effectSubscription details, new items and totals
subscription.pausedThe subscription was pausedWhen a pause request completesSubscription details
subscription.resumedThe subscription was resumedWhen a resume request completesSubscription details, next billing date
subscription.cancelledThe subscription was cancelledWhen an immediate cancellation completes, or a scheduled cancellation takes effectSubscription details, cancellation reason
subscription.expiredThe subscription reached its end dateWhen billing.date_end passesSubscription details
subscription.failedBilling stopped after exhausted retriesWhen all retries of a failed cycle are exhaustedSubscription details, last decline code and message

Webhook Security

All webhooks are sent with the following headers for verification:

  • X-Signature: HMAC-SHA256 signature of the payload
  • X-Timestamp: Unix timestamp of when the webhook was sent
  • User-Agent: Kwik-Webhooks/1.0

Webhook Response

Your endpoint should respond with a 200 status code to acknowledge receipt. Failed webhooks will be retried up to 3 times with exponential backoff.