# Create a Account Source: https://docs.momentco.io/api-reference/billing/accounts/create POST /billing/accounts Creates a new account for an existing or new customer. Accounts provide sub-organization within customer billing, allowing for separate balance tracking and payment rules. # Account object Source: https://docs.momentco.io/api-reference/billing/accounts/object Account object is used to represent a account. # Retrieve a specific Account Source: https://docs.momentco.io/api-reference/billing/accounts/retrieve GET /billing/accounts/{account} Retrieves detailed information about a specific account by its unique identifier or external reference. # Update a specific Account Source: https://docs.momentco.io/api-reference/billing/accounts/update PATCH /billing/accounts/{account} Updates an existing account's information. Only provided fields will be updated. # Void a specific Bill Source: https://docs.momentco.io/api-reference/billing/bills/cancel POST /billing/bills/{bill}/void Permanently voids a bill, making it uncollectable. A voided bill cannot be paid or updated. This operation is irreversible and should be used when a bill needs to be cancelled. # Create a Bill Source: https://docs.momentco.io/api-reference/billing/bills/create POST /billing/bills Creates a new bill for a customer or account. Bills represent payment requests that can be associated directly with customers or with specific accounts under customers. # Bill object Source: https://docs.momentco.io/api-reference/billing/bills/object Bill object is used to represent a Bill. # Retrieve a specific Bill Source: https://docs.momentco.io/api-reference/billing/bills/retrieve GET /billing/bills/{bill} Retrieves detailed information about a specific bill by its unique identifier or external reference. # Update a specific Bill Source: https://docs.momentco.io/api-reference/billing/bills/update PATCH /billing/bills/{bill} Updates an existing bill's information. Only provided fields will be updated. # Create a Customer Source: https://docs.momentco.io/api-reference/billing/customers/create POST /billing/customers Creates a new customer for billing and account management purposes. Customers serve as the primary entity for organizing accounts and bills within the billing system. # Customer object Source: https://docs.momentco.io/api-reference/billing/customers/object Customer object is used to represent a customer. # Retrieve a specific customer Source: https://docs.momentco.io/api-reference/billing/customers/retrieve GET /billing/customers/{customer} Retrieves detailed information about a specific customer by their unique identifier or external reference. # Update a specific customer Source: https://docs.momentco.io/api-reference/billing/customers/update PATCH /billing/customers/{customer} Updates an existing customer's information. Only provided fields will be updated. # Common Operations Source: https://docs.momentco.io/api-reference/billing/examples Common Billing API request and response examples ## Update a customer balance Use this to refresh what a customer owes at the start of a new billing cycle. Send the new `balance` value in minor units. The request replaces the existing balance on the customer. A positive value represents an amount owed by the customer; a negative value represents a credit balance. ```bash theme={"system"} curl -X PATCH https://api.momentpay.net/billing/customers/bcus_H4jp6KnU5cPw263v1jyz4 \ -H "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "balance": 5000 }' ``` The response returns the full updated customer record with the new `balance` reflected. ```json theme={"system"} { "id": "bcus_H4jp6KnU5cPw263v1jyz4", "external_reference": "UCR1234567890", "name": "John Doe", "email": "john.doe@example.com", "currency": "ZAR", "balance": 5000 } ``` *** ## Update an account balance Use this to refresh the balance on a specific account within a customer. Send the new `balance` value in minor units. Only the targeted account is affected; other accounts under the same customer are unchanged. A positive value represents an amount owed; a negative value represents a credit on the account. ```bash theme={"system"} curl -X PATCH https://api.momentpay.net/billing/accounts/bacc_H4jp6KnU5cPw263v1jyz4 \ -H "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "balance": 10000 }' ``` The response returns the full updated account record with the new `balance` reflected. ```json theme={"system"} { "id": "bacc_H4jp6KnU5cPw263v1jyz4", "external_reference": "UAR1234567890", "customer_id": "bcus_H4jp6KnU5cPw263v1jyz4", "name": "Subscription Account", "currency": "ZAR", "balance": 10000 } ``` *** ## Update a bill Use this to record a partial or full payment against a bill outside of the webhook flow. Send the `amount_paid` value in minor units. Moment recalculates `amount_remaining` automatically and updates `status` to `paid` if the bill is fully settled. ```bash theme={"system"} curl -X PATCH https://api.momentpay.net/billing/bills/bill_H4jp6KnU5cPw263v1jyz4 \ -H "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "amount_paid": 1500 }' ``` The response returns the full updated bill record. In this example a partial payment of 1500 has been applied against an `amount_due` of 2000, leaving `amount_remaining` of 500 and a status of `unpaid`. ```json theme={"system"} { "id": "bill_H4jp6KnU5cPw263v1jyz4", "external_reference": "UBR1234567890", "customer_id": "bcus_H4jp6KnU5cPw263v1jyz4", "account_id": "bacc_H4jp6KnU5cPw263v1jyz4", "status": "unpaid", "currency": "ZAR", "amount_due": 2000, "amount_paid": 1500, "amount_remaining": 500, "bill_date": "2025-01-01", "due_date": "2025-01-31" } ``` In a webhook-driven integration, `amount_paid` is updated automatically when Moment receives a payment and sends an `obligation.amount_applied` event. Use this endpoint for manual reconciliation or corrections. # Overview Source: https://docs.momentco.io/api-reference/billing/overview API reference for the Billing APIs The **Billing APIs** allow merchants to store and manage their customers, accounts, and bills within the Moment ecosystem, providing a foundation for building end-to-end billing experiences from account setup and bill generation to real-time tracking of payments and balances.
Accept a payment against a customer balance. Track balances across sub-accounts within a customer. Raise a bill with an amount due and track payment against it. Examples for updating customer balances, account balances, and bills.

Moment supports three billing models: **Customer Balance** (a single running balance per customer), **Account Balance** (separate balances across sub-accounts within a customer), and **Bill** (discrete invoices with their own amount due and lifecycle).

The right model depends on your use case. See [Core Concepts](/documentation/billing/core-concepts) for a full explanation.

πŸ‘‰ [See Product Guide](/documentation/billing/overview) *** ## APIs
Create and manage top-level customer records. Manage sub-customer accounts within a customer. Create, update, and track individual bills.
*** ## Webhooks The **Billing APIs** support webhook events that help you track billing-related lifecycle events in real time, including bill creation, status updates, and payment application. These events can be used to trigger downstream workflows, notify customers, or reconcile internal records. ### Available Events * **`obligation.amount_applied`** Triggered when an amount is received against an obligation (e.g., bill, customer, or account). πŸ‘‰ [See `obligation.amount_applied` Event](/api-reference/billing/webhook/obligation_amount_applied) Events like `bill.created`, `bill.updated`, `customer.created`, and others are coming soon. ### Error Codes πŸ‘‰ [See Common Error Codes](/api-reference/getting-started/errors) # Quickstart: Account Balance Source: https://docs.momentco.io/api-reference/billing/quickstart-account-balance Track balances across sub-accounts within a customer using the Billing API This guide walks through the account balance model: creating a customer, creating one or more accounts under that customer each with their own balance, and handling the payment webhook. This model is suited for merchants who need to track balances separately per product, service, or cost centre within a single customer. ## Prerequisites * A test API key. See [Authentication](/api-reference/getting-started/authentication). * A registered webhook endpoint. See [Webhook Setup](/api-reference/webhooks/setup) to register your URL and obtain your signing key. *** ## Step 1: Create a customer Create the top-level customer record. At this level, no balance is required. Balances are tracked on the accounts below. ```bash theme={"system"} curl -X POST https://api.momentpay.net/billing/customers \ -H "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "external_reference": "UCR1234567890", "name": "John Doe", "email": "john.doe@example.com" }' ``` The response returns an `id`. Store this to reference the customer when creating accounts. ```json theme={"system"} { "id": "bcus_H4jp6KnU5cPw263v1jyz4", "external_reference": "UCR1234567890", "name": "John Doe", "email": "john.doe@example.com" } ``` *** ## Step 2: Create an account Create an account under the customer with a `balance` representing what is owed on this account. ```bash theme={"system"} curl -X POST https://api.momentpay.net/billing/accounts \ -H "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "customer": "bcus_H4jp6KnU5cPw263v1jyz4", "external_reference": "UAR1234567890", "name": "Subscription Account", "currency": "ZAR", "balance": 2000 }' ``` `balance` is specified in minor units. See [Monetary Amounts](/api-reference/getting-started/monetary_amounts) for details. The response returns an `id`. Store this to reference the account when presenting it for payment. ```json theme={"system"} { "id": "bacc_H4jp6KnU5cPw263v1jyz4", "external_reference": "UAR1234567890", "customer_id": "bcus_H4jp6KnU5cPw263v1jyz4", "name": "Subscription Account", "currency": "ZAR", "balance": 2000 } ``` The customer and account can be created in a single request.

Instead of passing a `customer` ID, pass a `customer` object with the same fields used in the create customer request.

Moment creates the customer first and then creates the account, returning both IDs in the response. See [Create Account](/api-reference/billing/accounts/create#body-customer-one-of-2).
*** ## Step 3: Present the account balance for payment Present the account balance for payment using [Electronic Bill Presentment](/documentation/billing/ebp) or a [Payment Request](/documentation/collect/payment-links/payment-requests). *** ## Step 4: Handle the payment webhook When payment is received against the account, Moment sends an `obligation.amount_applied` event. ```json theme={"system"} { "id": "evt_2341234567890abcdef", "type": "obligation.amount_applied", "data": { "id": "ob_N2a38DJQWPL8K4X42354234", "amount": 2000, "currency": "ZAR", "payment_id": "pay_1313235234843nmafdsa", "account_id": "bacc_H4jp6KnU5cPw263v1jyz4" } } ``` Use `data.account_id` to identify which account received the payment and `data.amount` to reconcile against the account balance. Before processing, verify the event signature. See [Webhook Verification](/api-reference/webhooks/verification). Respond with a `2xx` status code to acknowledge receipt. *** ## Next steps
Track a single running balance directly on a customer. Raise a specific bill with an amount due and a due date. Present account balances to customers via a hosted payment page. Understand how customers, accounts, and bills relate to each other. Examples for updating customer balances, account balances, and bills.
# Quickstart: Bill Amount Due Source: https://docs.momentco.io/api-reference/billing/quickstart-bill-amount-due Raise a bill and track payment against it using the Billing API This guide walks through the bill model: creating a customer, creating an account under that customer, raising a bill against the account with a specific amount due and due date, and handling the payment webhook. This model is suited for merchants who issue discrete invoices, where each bill represents a specific charge with its own lifecycle and payment status. ## Prerequisites * A test API key. See [Authentication](/api-reference/getting-started/authentication). * A registered webhook endpoint. See [Webhook Setup](/api-reference/webhooks/setup) to register your URL and obtain your signing key. *** ## Step 1: Create a customer Create the top-level customer record. ```bash theme={"system"} curl -X POST https://api.momentpay.net/billing/customers \ -H "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "external_reference": "UCR1234567890", "name": "John Doe", "email": "john.doe@example.com" }' ``` The response returns an `id`. Store this to reference the customer when creating the account. ```json theme={"system"} { "id": "bcus_H4jp6KnU5cPw263v1jyz4", "external_reference": "UCR1234567890", "name": "John Doe", "email": "john.doe@example.com" } ``` *** ## Step 2: Create an account Create an account under the customer. Bills will be raised against this account. ```bash theme={"system"} curl -X POST https://api.momentpay.net/billing/accounts \ -H "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "customer": "bcus_H4jp6KnU5cPw263v1jyz4", "external_reference": "UAR1234567890", "name": "Primary Account" }' ``` The response returns an `id`. Store this to reference the account when creating the bill. ```json theme={"system"} { "id": "bacc_H4jp6KnU5cPw263v1jyz4", "external_reference": "UAR1234567890", "customer_id": "bcus_H4jp6KnU5cPw263v1jyz4", "name": "Primary Account" } ``` Steps 1 and 2 can be combined into a single request.

Instead of passing a `customer` ID when creating an account, pass a `customer` object with the same fields used in the create customer request.

Moment creates the customer first and then creates the account. See [Create Account](/api-reference/billing/accounts/create#body-customer-one-of-2).
*** ## Step 3: Create a bill Raise a bill against the account with an `amount_due`, `bill_date`, and `due_date`. ```bash theme={"system"} curl -X POST https://api.momentpay.net/billing/bills \ -H "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "account": "bacc_H4jp6KnU5cPw263v1jyz4", "external_reference": "UBR1234567890", "currency": "ZAR", "amount_due": 2000, "bill_date": "2025-01-01", "due_date": "2025-01-31" }' ``` `amount_due` is specified in minor units. See [Monetary Amounts](/api-reference/getting-started/monetary_amounts) for details. The response returns an `id` and an initial `status` of `unpaid`. Store the `id` to match against the payment webhook. ```json theme={"system"} { "id": "bill_H4jp6KnU5cPw263v1jyz4", "external_reference": "UBR1234567890", "customer_id": "bcus_H4jp6KnU5cPw263v1jyz4", "account_id": "bacc_H4jp6KnU5cPw263v1jyz4", "status": "unpaid", "amount_due": 2000, "amount_paid": 0, "amount_remaining": 2000, "bill_date": "2025-01-01", "due_date": "2025-01-31" } ``` Bills can also be raised directly against a customer without an account.

To do so, replace `"account"` with `"customer"` in the request body and use the customer `id` from Step 1.

In that case, the response will only include `customer_id` and not `account_id`.
All three steps can be combined into a single create bill request.

Instead of passing an `account` ID, pass an `account` object β€” and nest a `customer` object within it.

Moment creates the customer, then the account, then the bill in sequence. See [Create Bill](/api-reference/billing/bills/create#body-customer-one-of-2) for the customer object and [Create Bill](/api-reference/billing/bills/create#body-account-one-of-2) for the account object.
*** ## Step 4: Present the bill for payment Present the bill for payment using [Electronic Bill Presentment](/documentation/billing/ebp) or a [Payment Request](/documentation/collect/payment-links/payment-requests). *** ## Step 5: Handle the payment webhook When payment is received against the bill, Moment sends an `obligation.amount_applied` event. ```json theme={"system"} { "id": "evt_2341234567890abcdef", "type": "obligation.amount_applied", "data": { "id": "ob_N2a38DJQWPL8K4X42354234", "amount": 2000, "currency": "ZAR", "payment_id": "pay_1313235234843nmafdsa", "account_id": "bacc_H4jp6KnU5cPw263v1jyz4" } } ``` Use `data.account_id` to identify which account the bill belongs to, and `data.amount` to update the bill's payment status. If the bill was raised directly against a customer, the event will include `customer_id` instead of `account_id`. Before processing, verify the event signature. See [Webhook Verification](/api-reference/webhooks/verification). Respond with a `2xx` status code to acknowledge receipt. *** ## Next steps
Track a single running balance directly on a customer. Track balances across multiple sub-accounts within a customer. Present bills to customers via a hosted payment page. Ingest bills in bulk via SFTP or file integration. Examples for updating customer balances, account balances, and bills.
# Quickstart: Customer Balance Source: https://docs.momentco.io/api-reference/billing/quickstart-customer-balance Accept a payment against a customer balance using the Billing API This guide walks through the most common billing model: creating a customer with a balance and handling the payment webhook when that balance is settled. This model is suited for merchants who track a single running balance per customer without needing separate accounts or individual bills. ## Prerequisites * A test API key. See [Authentication](/api-reference/getting-started/authentication). * A registered webhook endpoint. See [Webhook Setup](/api-reference/webhooks/setup) to register your URL and obtain your signing key. *** ## Step 1: Create a customer Create a customer with a `balance` representing what they currently owe, in minor units. ```bash theme={"system"} curl -X POST https://api.momentpay.net/billing/customers \ -H "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "external_reference": "UCR1234567890", "name": "John Doe", "email": "john.doe@example.com", "currency": "ZAR", "balance": 2000 }' ``` `balance` is specified in minor units. See [Monetary Amounts](/api-reference/getting-started/monetary_amounts) for details. The response returns an `id`. Store this to reference the customer in future calls. ```json theme={"system"} { "id": "bcus_H4jp6KnU5cPw263v1jyz4", "external_reference": "UCR1234567890", "name": "John Doe", "email": "john.doe@example.com", "balance": 2000 } ``` *** ## Step 2: Present the balance for payment With the customer created, present their balance for payment using [Electronic Bill Presentment](/documentation/billing/ebp) or a [Payment Request](/documentation/collect/payment-links/payment-requests). *** ## Step 3: Handle the payment webhook When payment is received against the customer balance, Moment sends an `obligation.amount_applied` event. ```json theme={"system"} { "id": "evt_2341234567890abcdef", "type": "obligation.amount_applied", "data": { "id": "ob_N2a38DJQWPL8K4X42354234", "amount": 2000, "currency": "ZAR", "payment_id": "pay_1313235234843nmafdsa", "customer_id": "bcus_H4jp6KnU5cPw263v1jyz4" } } ``` Use `data.customer_id` to identify which customer paid and `data.amount` to reconcile against their balance. Before processing, verify the event signature. See [Webhook Verification](/api-reference/webhooks/verification). Respond with a `2xx` status code to acknowledge receipt. *** ## Next steps
Track balances across multiple sub-accounts within a customer. Raise a specific bill with an amount due and a due date. Present balances and bills to customers via a hosted payment page. Ingest customer balances and bills in bulk via SFTP or file integration. Examples for updating customer balances, account balances, and bills.
# Amount Applied Source: https://docs.momentco.io/api-reference/billing/webhook/obligation_amount_applied WEBHOOK obligation.amount_applied Triggered when an amount is received against an obligation (e.g., bill, customer, or account). **⚠️ Preview:** This webhook is under development and subject to change. # Changelog Source: https://docs.momentco.io/api-reference/changelog API changes, new fields, and updates across all Moment APIs ## Added Complete Payment Session endpoint and expanded Payment Method Options **New endpoint:** * `POST /collect/payment_sessions/{id}/complete` β€” update an existing Payment Session with a new payment method selection. Used in merchant-controlled flows to reuse the session after a failed attempt without creating a new one. Accepts `selected_payment_method` and `visible_payment_methods` using the same identifier formats as session creation. **New fields on `payment_method_options`:** * `mobile_money.phone_number` β€” pre-fills the customer's phone number for mobile money payment methods (for example M-Pesa, MTN MoMo, Airtel Money). Must be in E.164 format and match the session country. * `capitec_pay.phone_number` β€” pre-fills the customer's Capitec-registered phone number. Must be in E.164 format and match the session country. * `prefill_mode` β€” controls how the checkout handles pre-filled payment method fields. `review` presents the value for the customer to confirm; `auto_advance` skips the review step and advances automatically. **New field on `checkout_options`:** * `payment_method_selection` β€” controls who manages payment method selection during checkout. `gateway` (default) has Moment Checkout display and manage selection. `merchant` delegates selection to the merchant's own checkout; requires `return_url` and redirects back automatically after each payment attempt. πŸ‘‰ [See Complete Payment Session](/api-reference/collect/payment_sessions/complete) πŸ‘‰ [See Integrate Specific Payment Methods](/api-reference/collect/payment_sessions/quickstart-control-payment-methods) ## Renamed the `instant_eft` payment method type to `pay_by_bank` The Payment `payment_method_details` discriminated union variant previously identified as `instant_eft` is now `pay_by_bank`. This affects the `type` discriminator value, the nested details object key, and the `payment.succeeded` webhook payload. **What changed:** * `type: instant_eft` is now `type: pay_by_bank` * The `instant_eft` details object (containing `bank`) is now `pay_by_bank` πŸ‘‰ [See the Payment object reference](/api-reference/collect/payments/object) ## Consolidated in-flight Payment statuses under a single `pending` status The Payment `status` field now reports a single `pending` value for any payment with an in-flight attempt, instead of exposing the more granular `initiating`, `pending_action`, and `processing` values at the payment level. The detailed phase of an in-flight payment is an attempt-level concern and is no longer surfaced on the payment aggregate status. **New value on Payment `status`:** * `pending`: the payment has an in-flight attempt and is awaiting a final outcome **Deprecations:** * `initiating`, `pending_action`, and `processing` are deprecated as Payment `status` values. They remain valid enum members for backwards compatibility, but new payment responses and webhook payloads will not emit them. Integrations should treat any of these legacy values as `pending`. **What this means for webhooks:** Payments that previously appeared with `status: initiating`, `processing`, or `pending_action` will now appear with `status: pending`. Terminal statuses (`succeeded`, `failed`, `cancelled`, `expired`) and `draft` are unchanged. If your integration matches on the legacy in-flight values, map them to `pending`. πŸ‘‰ [See the Payment object reference](/api-reference/collect/payments/object) ## Introduced `payment_outcome` on Payment Sessions, deprecating `payment_status` The Payment Session resource now exposes a richer `payment_outcome` field that supersedes `payment_status`. The new field carries the existing `paid`, `unpaid`, and `pending` values and adds three more to cover flows that the binary `payment_status` could not express cleanly. **New field on the Payment Session object:** * `payment_outcome` β€” the canonical state of the payment associated with the session **Values:** * `not_started` β€” payment has not been initiated * `pending` β€” payment is in flight, awaiting a final outcome * `paid` β€” payment succeeded (terminal) * `unpaid` β€” payment failed, expired, or was cancelled (terminal) * `reserved` β€” funds were authorised on a `capture_method: manual` flow; subsequent capture and void operations are tracked on the underlying Payment resource * `payment_method_verified` β€” supported `amount: 0` verification flow succeeded and the payment method was vaulted **Deprecations:** * `payment_status` is now deprecated. It continues to be populated with `paid`, `unpaid`, or `pending` for backwards compatibility. New integrations should read `payment_outcome`. πŸ‘‰ [See Payment Session Lifecycle](/documentation/collect/payment-sessions/lifecycle) ## Added Authorisation and Capture and Payment Method Verification to Payments and Payment Sessions APIs Introduced support for authorisation and capture payment flows, and payment method verification, across both the Payments and Payment Sessions APIs. **Payments API β€” new endpoints:** * `POST /collect/payments/{id}/captures` β€” capture a previously authorised payment * `POST /collect/payments/{id}/voids` β€” cancel a previously authorised payment **Payments API β€” new fields on the Payment object:** * `capture_method` β€” whether the payment uses immediate or manual capture * `authorized_amount` β€” the amount held under authorisation * `paid_amount` β€” the amount captured and collected * `voided_amount` β€” the amount voided when an authorisation is cancelled * `intent` β€” indicates whether the payment was initiated for verification or payment collection * `metadata` now accepted on capture and void requests * `external_reference` enforces a pattern of `^[a-zA-Z0-9_-]+$` **Payment Sessions API:** * `payment_method_options` added to One Time, First In Series, and Next In Series payment sessions to configure the capture method and enable payment method verification flows πŸ‘‰ [See Payments Overview](/api-reference/collect/payments/overview) πŸ‘‰ [See Authorisation and Capture](/documentation/collect/payment-sessions/authorisation-capture/overview) πŸ‘‰ [See Payment Method Verification](/documentation/collect/payment-sessions/payment-method-verification) ## Added Mandate and Subscription Options to Payment Sessions API Payment sessions now support mandate and subscription options for recurring payment flows. Merchants can configure consent terms, validity periods, execution schedules, and payment amount controls when initiating a mandate. **Mandate options:** * `mandate_options` added to First In Series payment sessions * Supported mandate types: Scheduled, On Demand, and Installment * Execution constraints control when recurring payments can be collected: `day_of_month`, `day_of_week`, `day_of_year`, `nth_day_of_month` * Period limits define how much can be collected within a given window, with support for calendar-aligned and cycle-aligned windows * Amount can be fixed or variable within defined bounds **Subscription options:** * `subscription_options` added to support plan-driven recurring payments * Recurrence can be configured as daily, weekly, or monthly with flexible scheduling πŸ‘‰ [See Mandate Options](/documentation/collect/payment-sessions/mandates/overview) πŸ‘‰ [See Subscription Options](/documentation/collect/payment-sessions/subscriptions/overview) ## Added Payment Rules and Schema Refinements to Billing API **New fields on the Account object:** * `payment_rules.min_amount` β€” minimum payment amount accepted * `payment_rules.max_amount` β€” maximum payment amount accepted * `payment_rules.overpayment_allowed` β€” whether payments exceeding the balance are accepted * `payment_rules.underpayment_allowed` β€” whether partial payments are accepted **Additional changes:** * `external_reference` on accounts now excludes the reserved `bacc_` prefix * `external_reference` on customers and bills now excludes the reserved `bcus_` prefix * `conflict` and `payment_not_found` error codes added across all billing endpoints πŸ‘‰ [See Account Object](/api-reference/billing/accounts/object) ## Added Presentation Mode to Payment Sessions API `presentation_mode` is now available on `checkout_options` in the Payment Sessions API to control how the Moment checkout is presented to customers. **Supported modes:** * `redirect` β€” redirects the customer to the Moment-hosted checkout page * `embedded_modal` β€” displays checkout as a modal overlay within the merchant's page * `embedded_inline` β€” renders checkout inline within the merchant's page **Additional changes:** * `display_pay_button` replaces the previous `displayPayButton` field on embedded mode options * `payment_not_found` added as a new error code across create and retrieve endpoints πŸ‘‰ [See Presentation Mode](/api-reference/collect/payment_sessions/create#body-options-one-of-0-checkout-options-presentation-mode-one-of-0) ## Introduced Payments API Initial release of the Payments API. **Endpoints:** * `GET /collect/payments/{id}` β€” retrieve the full Payment object by its identifier The Payment object includes the payment status, amount, currency, country, mode, payment method details, external reference, last payment error, metadata, and timestamps. **Notable:** `payment_method_details` is returned as a discriminated union β€” each payment method type (Card, Capitec Pay, Instant EFT, Zapper, MTN MoMo) is a distinct subschema identified by a `type` discriminator. πŸ‘‰ [See Payment Object](/api-reference/collect/payments/object) πŸ‘‰ [See Payments Overview](/api-reference/collect/payments/overview) ## Introduced Payment Requests API Initial release of the Payment Requests API, enabling merchants to create and share direct payment links with customers via SMS, email, or messaging apps. **Endpoints:** * `POST /collect/payment_requests` β€” create a payment request * `GET /collect/payment_requests/{id}` β€” retrieve a payment request * `PATCH /collect/payment_requests/{id}` β€” update a payment request * `DELETE /collect/payment_requests/{id}` β€” delete a payment request **Webhooks:** * `payment_request.created` β€” triggered when a payment request is created * `payment_request.updated` β€” triggered when a payment request is updated * `payment_request.paid` β€” triggered when a payment request is paid πŸ‘‰ [See Payment Requests Overview](/api-reference/collect/payment_requests/overview) ## Added `obligation.amount_applied` Webhook to Billing API The `obligation.amount_applied` webhook event is now available, triggered when an amount is received against a billing obligation such as a bill, account, or customer. πŸ‘‰ [See obligation.amount\_applied](/api-reference/billing/webhook/obligation_amount_applied) ## Introduced Payment Pages API Initial release of the Payment Pages API, enabling merchants to create branded hosted payment pages that customers can access via a shareable link. **Endpoints:** * `POST /collect/payment_pages` β€” create a payment page * `GET /collect/payment_pages/{id}` β€” retrieve a payment page * `PATCH /collect/payment_pages/{id}` β€” update a payment page * `DELETE /collect/payment_pages/{id}` β€” delete a payment page * `PATCH /collect/payment_pages/{id}/inactive` β€” deactivate a payment page * `GET /collect/payment_pages/{id}/transactions` β€” retrieve transactions for a payment page **Webhooks:** * `payment_page.payment_received` β€” triggered when a payment is received on a hosted page πŸ‘‰ [See Payment Pages Overview](/api-reference/collect/payment_pages/overview) ## Introduced Billing API Initial release of the Billing API, providing full lifecycle management of customers, accounts, and bills. **Customers:** * `POST /billing/customers` β€” create a customer * `GET /billing/customers/{customer}` β€” retrieve a customer * `PATCH /billing/customers/{customer}` β€” update a customer **Accounts:** * `POST /billing/accounts` β€” create an account under a customer * `GET /billing/accounts/{account}` β€” retrieve an account * `PATCH /billing/accounts/{account}` β€” update an account **Bills:** * `POST /billing/bills` β€” create a bill * `GET /billing/bills/{bill}` β€” retrieve a bill * `PATCH /billing/bills/{bill}` β€” update a bill * `POST /billing/bills/{bill}/void` β€” cancel a bill πŸ‘‰ [See Billing Overview](/api-reference/billing/overview) ## Added Checkout Options to Payment Sessions API `checkout_options` added to One Time and First In Series payment sessions, allowing merchants to configure the checkout experience presented to customers. πŸ‘‰ [See Checkout Options](/api-reference/collect/payment_sessions/create#body-options-one-of-0-checkout-options) ## Introduced Payment Sessions API Initial release of the Payment Sessions API. **Endpoints:** * `POST /collect/payment_sessions` β€” create a new payment session * `GET /collect/payment_sessions/{id}` β€” retrieve a payment session by its identifier Payment sessions support three flow types, each with its own configuration: * **One Time** β€” a single payment flow with checkout and payment method options * **First In Series** β€” initiates a recurring payment flow and captures the mandate * **Next In Series** β€” continues a recurring payment flow using an existing mandate πŸ‘‰ [See Payment Sessions Overview](/api-reference/collect/payment_sessions/overview) # Overview Source: https://docs.momentco.io/api-reference/collect/overview API reference for the Collect product The **Collect APIs** provide support for merchants to collect payments from their customers across multiple payment methods. ## APIs
Create and manage payment sessions for one-time, recurring, and authorisation and capture flows. Generate shareable payment links and branded hosted pages. Retrieve, capture, and void payments.
# Overview Source: https://docs.momentco.io/api-reference/collect/payment_links/overview API reference for Payment Links The **Payment Links APIs** let you create shareable links and hosted pages that customers can use to complete payment, without requiring a custom checkout integration.
Generate one-time payment links or trigger bank push requests directly to a customer's banking app. Create and manage reusable branded pages that accept multiple payments.
# Create a Payment Page Source: https://docs.momentco.io/api-reference/collect/payment_pages/create POST /collect/payment_pages Creates a new payment page for collecting payments from customers. # Delete a Payment Page Source: https://docs.momentco.io/api-reference/collect/payment_pages/delete DELETE /collect/payment_pages/{id} Permanently deletes a payment page, making it unavailable for new payments. Only payment pages **with no successful or attempted payments** can be deleted. If the page has processed any payments, it **cannot be deleted** due to audit and reconciliation requirements. ➀ Instead, [mark the page as inactive](/api-reference/collect/payment-pages/inactive) to stop new payments while keeping it available for reference. # Change a Payment Page to Inactive Source: https://docs.momentco.io/api-reference/collect/payment_pages/inactive PATCH /collect/payment_pages/{id}/inactive Marks a payment page as inactive, preventing new payments but keeping the page in record for reference. # Payment Page object Source: https://docs.momentco.io/api-reference/collect/payment_pages/object Payment Page object is used to represent a Payment Page. # Overview Source: https://docs.momentco.io/api-reference/collect/payment_pages/overview The **Payment Pages API** enables merchants to create customizable, branded payment pages that can be shared with customers across any channel. These pages are mobile-optimized, secure, and support multiple payment methods β€” ideal for donations, event tickets, product sales, and more, all without needing a full website or integration. πŸ‘‰ [See Product Guide](/documentation/collect/payment-links/payment-pages) *** ## **Webhooks** The **Payment Pages API** supports webhook events that notify you in real time as the page status or associated payments change. These events allow you to automate business workflows β€” such as triggering CRM updates, sending customer confirmations, or recording payments in your internal systems. ### **Available Events** * **`payment_page.payment_received`**\ Triggered when a Payment Page has successfully received a payment.\ πŸ‘‰ [See `payment_page.payment_received` Event](/api-reference/collect/payment_pages/webhook/payment_received) *** ### **Error Codes** πŸ‘‰ [See Common Error Codes](/api-reference/getting-started/errors) # Payment object Source: https://docs.momentco.io/api-reference/collect/payment_pages/payments/object Payment object is used to represent a Payment from a Payment Page. # List all Payments Source: https://docs.momentco.io/api-reference/collect/payment_pages/payments/retrieve_all GET /collect/payment_pages/{id}/transactions Retrieves all transactions associated with a specific payment page. This endpoint uses cursor-based pagination. Use the `Next-Cursor` and `Prev-Cursor` headers to paginate through results. # Retrieve a Payment Page Source: https://docs.momentco.io/api-reference/collect/payment_pages/retrieve GET /collect/payment_pages/{id} Retrieves detailed information about a specific payment page by its unique identifier. # Update a Payment Page Source: https://docs.momentco.io/api-reference/collect/payment_pages/update PATCH /collect/payment_pages/{id} Updates an existing payment page. # Payment Received Source: https://docs.momentco.io/api-reference/collect/payment_pages/webhook/payment_received WEBHOOK payment_page.payment_received Triggered when a payment is successfully received via a Payment Page. # Create a Payment Request Source: https://docs.momentco.io/api-reference/collect/payment_requests/create POST /collect/payment_requests Creates a new Payment Request for collecting payments from customers. # Delete a Payment Request Source: https://docs.momentco.io/api-reference/collect/payment_requests/delete DELETE /collect/payment_requests/{id} Permanently deletes a Payment Request, making it unavailable for payments. A Payment Request can not be deleted if the status is paid # Payment Request object Source: https://docs.momentco.io/api-reference/collect/payment_requests/object Payment Request object is used to represent a Payment Request. # Overview Source: https://docs.momentco.io/api-reference/collect/payment_requests/overview The **Payment Requests API** provides a simple, shareable, and secure way for merchants to collect payments from their customers. With a single API, you can generate unique **links** or trigger a **bank push** that directs customers to complete paymentβ€”no website or heavy integration required. πŸ‘‰ [See Product Guide](/documentation/collect/payment-links/payment-requests) ## **Webhooks** The **Payment Requests API** emits webhook events so you can track the lifecycle of a Payment Request (Link or Push) in real time and automate downstream actions like updating internal systems, notifying customers, or triggering workflows. ### **Available Events** * **`payment_request.created`**\ Triggered when a Payment Request is created and becomes **active** (ready to be shared or authorized).\ πŸ‘‰ [See `payment_request.created` Event](/api-reference/collect/payment_requests/webhook/created) * **`payment_request.updated`**\ Triggered when a Payment Request’s attributes or status change (e.g., description/expiry updates, etc.).\ πŸ‘‰ [See `payment_request.updated` Event](/api-reference/collect/payment_requests/webhook/updated) * **`payment_request.paid`**\ Triggered when the Payment Request is successfully paid.\ πŸ‘‰ [See `payment_request.paid` Event](/api-reference/collect/payment_requests/webhook/paid) ### **Error Codes** πŸ‘‰ [See Common Error Codes](/api-reference/getting-started/errors) # Retrieve a Payment Request Source: https://docs.momentco.io/api-reference/collect/payment_requests/retrieve GET /collect/payment_requests/{id} Retrieves detailed information about a specific Payment Request by its unique identifier. # Update a Payment Request Source: https://docs.momentco.io/api-reference/collect/payment_requests/update PATCH /collect/payment_requests/{id} Updates an existing Payment Request. Only Payment Requests that haven't been paid or expired can be updated. # Payment Request Created Source: https://docs.momentco.io/api-reference/collect/payment_requests/webhook/created WEBHOOK payment_request.created Triggered when a new Payment Request is successfully created. # Payment Request Paid Source: https://docs.momentco.io/api-reference/collect/payment_requests/webhook/paid WEBHOOK payment_request.paid Triggered when an existing Payment Request is successfully paid. # Payment Request Updated Source: https://docs.momentco.io/api-reference/collect/payment_requests/webhook/updated WEBHOOK payment_request.updated Triggered when an existing Payment Request is updated. # Complete a Payment Session Source: https://docs.momentco.io/api-reference/collect/payment_sessions/complete POST /collect/payment_sessions/{id}/complete Update an existing Payment Session with a new payment method selection, clear a previous selection, or update pre-filled payment method details such as a phone number. Used in merchant-controlled flows to reuse the session after a failed payment attempt without creating a new one. Pass `selected_payment_method`, `visible_payment_methods`, or both to update the selection. Pass either field as `null` to clear a previously set value. Pass `payment_method_options` to update pre-filled details for the selected payment method. # Create a Payment Session Source: https://docs.momentco.io/api-reference/collect/payment_sessions/create POST /collect/payment_sessions Create a payment session. # Payment Session object Source: https://docs.momentco.io/api-reference/collect/payment_sessions/object Payment Session object is used to represent a payment session. # Overview Source: https://docs.momentco.io/api-reference/collect/payment_sessions/overview The **Payment Sessions API** provides a single, uniform interface for merchants to accept payments from their customers, including one-time charges, subscription setup payments, and repeat payments using previously saved details. πŸ‘‰ [See Product Guide](/documentation/collect/payment-sessions)
Create a payment session, launch checkout, and handle the completion webhook. Set up a mandate and collect future payments without customer interaction. Control which payment methods appear on the hosted checkout, or manage selection yourself.
## **Webhooks** The **Payment Sessions API** supports webhook events that allow you to track the lifecycle and state changes of a payment session in real time. These webhooks can be used to automate internal processes, trigger notifications, or initiate subsequent payment flows. ### **Available Events** * **`payment_session.created`**\ Triggered when a Payment Session is successfully created and ready to be used for payment.\ πŸ‘‰ [See `payment_session.created` Event](/api-reference/collect/payment_sessions/webhook/created) * **`payment_session.updated`**\ Triggered when a Payment Session is updated.\ πŸ‘‰ [See `payment_session.updated` Event](/api-reference/collect/payment_sessions/webhook/updated) * **`payment_session.completed`**\ Triggered when a Payment Session has been completed successfully.\ πŸ‘‰ [See `payment_session.completed` Event](/api-reference/collect/payment_sessions/webhook/completed) The event payload includes a `payment_session` object, which contains fields like `created_at`, `updated_at`, and `payment_id`. Where applicable, it also includes a `customer_id` and `payment_method_id`, which can be used for subsequent **next-in-series** payments. ### Error Codes πŸ‘‰ [See Common Error Codes](/api-reference/getting-started/errors) # Quickstart: Integrate Specific Payment Methods Source: https://docs.momentco.io/api-reference/collect/payment_sessions/quickstart-control-payment-methods Control which payment methods appear on the hosted checkout Merchants can control which payment methods appear on the hosted checkout and how payment method selection is managed.

There are two integration modes, configured using [`payment_method_selection`](/api-reference/collect/payment_sessions/create#body-options-one-of-0-checkout-options-payment-method-selection) in `options.checkout_options`: * **Gateway mode:** Moment Checkout displays and manages payment method selection. The customer chooses how to pay within the hosted checkout. This is the default and the recommended pattern. * **Merchant-controlled mode:** Your checkout manages payment method selection. When the customer proceeds, you create a Payment Session for the selected method and redirect to Moment Checkout to complete the payment.
This guide builds on the [One-Time Payments quickstart](/api-reference/collect/payment_sessions/quickstart-one-time). If you have not created a one-time payment session before, start there.
This quickstart applies to **customer-initiated payments (CIT)**, where the customer is present and completes payment using Moment Checkout. It applies to Payment Sessions created with the `one_time` and `first_in_series` payment types.

For **merchant-initiated payments (MIT)** using the `next_in_series` payment type, see the recurring payments guide. These payments are processed without launching Moment Checkout.
## Prerequisites * A test API key. See [Authentication](/api-reference/getting-started/authentication). ## Gateway mode `payment_method_selection` defaults to `gateway` and does not need to be set explicitly.

Create a Payment Session with the payment methods you want to make available in `visible_payment_methods`, then redirect the customer to the returned `session_url`. Moment Checkout displays the available methods, manages selection, and completes the payment. On payment failure, the customer stays within Moment Checkout and can retry or switch to another available method without returning to your checkout.

This is the recommended pattern because Moment Checkout manages the complete payment experience, which typically gives better conversion with the least integration effort. ```mermaid theme={"system"} sequenceDiagram participant Customer participant MerchantCheckout as Merchant Checkout participant MerchantServer as Merchant Server participant MomentAPI as Moment Payment Sessions API participant MomentCheckout as Moment Checkout Customer->>MerchantCheckout: Clicks pay MerchantCheckout->>MerchantServer: Request payment session MerchantServer->>MomentAPI: Create Payment Session
payment_method_selection: gateway (default)
visible_payment_methods: [methods] MomentAPI-->>MerchantServer: Return session_url MerchantServer-->>MerchantCheckout: Return session_url MerchantCheckout->>MomentCheckout: Redirect customer MomentCheckout->>Customer: Display available payment methods Customer->>MomentCheckout: Select payment method and complete payment alt Payment succeeds rect rgb(220, 252, 231) MomentCheckout-->>Customer: Show payment result MomentCheckout-->>MerchantCheckout: Redirect to return_url end else Payment fails rect rgb(254, 226, 226) Note over MomentCheckout,Customer: Moment handles failure β€” customer stays in hosted checkout MomentCheckout-->>Customer: Show failure, offer retry or switch method Customer->>MomentCheckout: Retry or select another available method MomentCheckout->>Customer: Complete payment MomentCheckout-->>MerchantCheckout: Redirect to return_url end end ``` ### Step 1 (optional): Restrict the visible payment methods Add `visible_payment_methods` to `options.checkout_options`. Pass an array of the payment method types you want to display. Only these methods will appear on the checkout. For example, to support Capitec Pay (`capitec_pay`) and Card (`card`): ```shellscript theme={"system"} curl -X POST https://api.momentpay.net/collect/payment_sessions \ -H "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "amount": 10000, "currency": "ZAR", "type": "one_time", "options": { "checkout_options": { "visible_payment_methods": ["capitec_pay", "card"] } } }' ``` The response is the same as a standard one-time session. The `id` can be used to match the webhook event. ```json theme={"system"} { "id": "ps_kfAWZgfQIoeG0q", "status": "active", "payment_outcome": "not_started", "amount": 10000, "currency": "ZAR", "session_url": "https://checkout.momentpay.net/ckt802zEb2P2uS4Ql", "created_at": "2025-03-18T14:06:51.149Z", "updated_at": "2025-03-18T14:06:51.149Z" } ``` Redirect the customer to the `session_url` returned when the Payment Session is created. An alternative to redirecting is to use our SDK to implement a modal or pop-up styled hosted checkout. See [Libraries and SDKs](/libraries-and-sdks/web/javascript). The checkout will be restricted as follows: Screenshot 2026 07 09 At 8 03 13 PM If you pass only one payment method in `visible_payment_methods`, that method is pre-selected and the customer is taken straight into that payment method's journey. For example, if `cards` is the only method passed, the customer is taken directly to the card details page. ### Step 2 (optional): Pre-select a payment method When `visible_payment_methods` contains more than one method, you can pre-select one of them with `selected_payment_method`. The customer is taken directly into that payment method's journey without having to choose on the selection screen. ```json theme={"system"} "options": { "checkout_options": { "visible_payment_methods": ["capitec_pay", "card"], "selected_payment_method": "capitec_pay" } } ``` The checkout will directly present Capitec Pay (`capitec_pay`), but Card (`card`) remains available if the customer chooses that instead: Screenshot 2026 07 09 At 8 02 41 PM ## Merchant-controlled mode Set `payment_method_selection` to `merchant` in `options.checkout_options` to enable this mode. A `return_url` is required. On payment failure, Moment automatically redirects the customer back to your `return_url` so your checkout can handle the outcome.

Your checkout displays and manages payment method selection. When the customer proceeds, create a Payment Session with `visible_payment_methods`, `selected_payment_method`, or both. Moment Checkout opens directly into the selected method's flow.

On failure, the customer returns to your checkout. Rather than creating a new Payment Session, call [Complete Payment Session](/api-reference/collect/payment_sessions/complete) on the existing session with the customer's new selection. Moment Checkout redirects the customer to the updated method. ```mermaid theme={"system"} sequenceDiagram participant Customer participant MerchantCheckout as Merchant Checkout participant MerchantServer as Merchant Server participant MomentAPI as Moment Payment Sessions API participant MomentCheckout as Moment Checkout MerchantCheckout->>Customer: Display available payment methods Customer->>MerchantCheckout: Select payment method MerchantCheckout->>MerchantServer: Request session for selected method MerchantServer->>MomentAPI: Create Payment Session
payment_method_selection: merchant
visible_payment_methods / selected_payment_method MomentAPI-->>MerchantServer: Return session_url MerchantServer-->>MerchantCheckout: Return session_url MerchantCheckout->>MomentCheckout: Redirect customer MomentCheckout->>Customer: Open selected payment method flow Customer->>MomentCheckout: Complete payment attempt alt Payment succeeds rect rgb(220, 252, 231) MomentCheckout-->>MerchantCheckout: Redirect to return_url MerchantCheckout->>Customer: Display payment confirmation end else Payment fails rect rgb(254, 226, 226) Note over MomentCheckout,MerchantCheckout: Moment redirects back β€” merchant handles retry MomentCheckout-->>MerchantCheckout: Redirect to return_url (automatic) MerchantCheckout->>Customer: Display payment methods Customer->>MerchantCheckout: Select another payment method MerchantCheckout->>MerchantServer: Update existing session with new selection MerchantServer->>MomentAPI: POST /payment_sessions/{id}/complete
selected_payment_method / visible_payment_methods MomentAPI-->>MerchantServer: Return updated session_url MerchantServer-->>MerchantCheckout: Return session_url MerchantCheckout->>MomentCheckout: Redirect customer to new selection end end ``` ### Step 1: Display payment methods in your checkout Display the payment methods you want to make available using your own checkout interface. Your integration is responsible for determining which payment methods to display and mapping each option to its corresponding Moment payment method type. ### Step 2: Create a Payment Session for the selected method When the customer selects a payment method and proceeds to pay, create a Payment Session from your server. Pass `visible_payment_methods`, `selected_payment_method`, or both. Set `payment_method_selection` to `merchant` and include a `return_url` so Moment knows where to redirect the customer after each payment attempt. For example, if the customer selects Capitec Pay: ```shellscript theme={"system"} curl -X POST https://api.momentpay.net/collect/payment_sessions \ -H "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "amount": 10000, "currency": "ZAR", "type": "one_time", "options": { "checkout_options": { "visible_payment_methods": ["capitec_pay"], "payment_method_selection": "merchant", "return_url": "https://merchant.example/checkout/order_123/payment-result" } }, "external_reference": "order_123" }' ``` The response is the same as a standard one-time session. The `id` can be used to match the webhook event. ```json theme={"system"} { "id": "ps_kfAWZgfQIoeG0q", "status": "active", "payment_outcome": "not_started", "amount": 10000, "currency": "ZAR", "session_url": "https://checkout.momentpay.net/ckt802zEb2P2uS4Ql", "created_at": "2025-03-18T14:06:51.149Z", "updated_at": "2025-03-18T14:06:51.149Z" } ``` Redirect the customer to the `session_url` returned when the Payment Session is created. An alternative to redirecting is to use our SDK to implement a modal or pop-up styled hosted checkout. See [Libraries and SDKs](/libraries-and-sdks/web/javascript). The checkout will directly present Capitec Pay (`capitec_pay`) as the only option: Screenshot 2026 07 09 At 8 02 41 PM ### Step 3 (optional): Pre-fill payment method details When you know the customer's phone number in advance (for example from a previous session or their account profile), you can pass it when creating the Payment Session to reduce the steps required to complete the payment. This applies to mobile money payment methods (for example M-Pesa, MTN MoMo, Airtel Money) and Capitec Pay. Pass the phone number under `payment_method_options` using the corresponding key (`mobile_money` or `capitec_pay`). The phone number must be in E.164 format and its country must match the session country. Use `prefill_mode` to control how the checkout handles the pre-filled value: * `review`: The pre-filled value is shown to the customer to confirm. They can edit it before proceeding. * `auto_advance`: The checkout skips the review step and advances automatically. Use this when you are confident the data is correct and want to minimise the steps to complete payment. For Capitec Pay: ```shellscript theme={"system"} curl -X POST https://api.momentpay.net/collect/payment_sessions \ -H "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "amount": 10000, "currency": "ZAR", "type": "one_time", "options": { "checkout_options": { "visible_payment_methods": ["capitec_pay"], "payment_method_selection": "merchant", "return_url": "https://merchant.example/checkout/order_123/payment-result" } }, "payment_method_options": { "capitec_pay": { "phone_number": "+27821234567" }, "prefill_mode": "auto_advance" }, "external_reference": "order_123" }' ``` For mobile money, replace `capitec_pay` with `mobile_money` and supply the customer's registered mobile money number: ```shellscript theme={"system"} curl -X POST https://api.momentpay.net/collect/payment_sessions \ -H "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "amount": 5000, "currency": "KES", "type": "one_time", "options": { "checkout_options": { "visible_payment_methods": ["mpesa"], "payment_method_selection": "merchant", "return_url": "https://merchant.example/checkout/order_456/payment-result" } }, "payment_method_options": { "mobile_money": { "phone_number": "+254701234567" }, "prefill_mode": "auto_advance" }, "external_reference": "order_456" }' ``` ### Step 4: Handle the customer's return After the payment attempt succeeds or fails, Moment Checkout automatically redirects the customer to the configured `return_url`. When the customer returns, retrieve the Payment Session to display the latest payment status. Use webhooks as the authoritative source for asynchronous payment status updates. If the payment was unsuccessful, display the payment method selection in your checkout. When the customer proceeds with a new or the same selection, call [Complete Payment Session](/api-reference/collect/payment_sessions/complete) on the existing session with the updated `selected_payment_method` and `visible_payment_methods`. This updates the session in place without requiring a new Payment Session to be created, then redirects the customer back to Moment Checkout for the new selection. ```shellscript theme={"system"} curl -X POST https://api.momentpay.net/collect/payment_sessions/ps_kfAWZgfQIoeG0q/complete \ -H "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "selected_payment_method": "card", "visible_payment_methods": ["card"] }' ``` ## Supported Payment Methods The following payment methods can be configured using `visible_payment_methods` and `selected_payment_method`: Use the payment method `category` and `type` above to control the visible and selected payment methods. For bank payment methods, use the `variant` from the table below to target a specific bank. ### Bank Variant Reference For `pay_by_bank`, you can target a single bank by passing `bank_payments.pay_by_bank.{variant}` in `visible_payment_methods`. Only that bank will appear on the checkout. | Bank | Variant | | - | - | | Standard Bank | `sbsa` | | Nedbank | `nedbank` | | FNB | `fnb` | | Tyme Bank | `tymebank` | | ABSA | `absa` | | Investec | `investec` | | Bidvest Bank | `bidvestbank` | | Bank | Variant | | - | - | | KCB Bank | `kcb` | | Equity Bank | `equitybank` | | Co-op Bank | `coopbank` | | Family Bank | `familybank` | | NCBA Bank | `ncba` | | National Bank | `nationalbank` | | Stanbic Bank | `stanbic` | | Standard Chartered | `standard_chartered` | | ABSA Bank | `absa` | | Diamond Trust Bank | `dtb` | | Bank | Variant | | - | - | | Access Bank | `accessbank` | | Ecobank | `ecobank` | | FCMB | `fcmb` | | Fidelity Bank | `fidelitybank` | | First Bank | `firstbank` | | Globus Bank | `globusbank` | | Guaranty Trust Bank | `gtb` | | Keystone Bank | `keystonebank` | | Stanbic IBTC | `stanbicibtcbank` | | Sterling Bank | `sterlingbank` | | UBA | `uba` | | Union Bank | `unionbank` | | Unity Bank | `unitybank` | | Zenith Bank | `zenithbank` | ### Payment method identifier formats `visible_payment_methods` and `selected_payment_method` accept identifiers in the following formats: | Format | Example | Effect | | - | - | - | | `category` | `bank_payments` | All types within the category | | `type` | `capitec_pay` | That specific payment method | | `category.type` | `bank_payments.capitec_pay` | Equivalent to `type` only | | `category.type.variant` | `bank_payments.pay_by_bank.kcb` | A specific bank (`pay_by_bank` only) | **Example: using `category.type`** ```shellscript highlight={10} theme={"system"} curl -X POST https://api.momentpay.net/collect/payment_sessions \ -H "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "amount": 10000, "currency": "ZAR", "type": "one_time", "options": { "checkout_options": { "visible_payment_methods": ["bank_payments.capitec_pay", "cards.card"] } } }' ``` **Example: using `category` only** ```shellscript highlight={10} theme={"system"} curl -X POST https://api.momentpay.net/collect/payment_sessions \ -H "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "amount": 10000, "currency": "ZAR", "type": "one_time", "options": { "checkout_options": { "visible_payment_methods": ["bank_payments", "cards"] } } }' ``` **Example: targeting a specific bank** ```shellscript highlight={10} theme={"system"} curl -X POST https://api.momentpay.net/collect/payment_sessions \ -H "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "amount": 10000, "currency": "KES", "type": "one_time", "options": { "checkout_options": { "visible_payment_methods": ["bank_payments.pay_by_bank.kcb"] } } }' ``` For `selected_payment_method`, the value must resolve to a single payment method and must be present in `visible_payment_methods`. How it can be specified depends on how `visible_payment_methods` was set: * If `visible_payment_methods` specifies only a `category`, `selected_payment_method` can be `category.type` or just `type`. * If `visible_payment_methods` specifies a `type`, `selected_payment_method` cannot be a category on its own. * If `visible_payment_methods` specifies a `category.type.variant`, `selected_payment_method` must use the same three-level format. **Example: invalid `selected_payment_method`** ```shellscript highlight={10-11} theme={"system"} curl -X POST https://api.momentpay.net/collect/payment_sessions \ -H "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "amount": 10000, "currency": "ZAR", "type": "one_time", "options": { "checkout_options": { "visible_payment_methods": ["capitec_pay", "card"], "selected_payment_method": "bank_payments" } } }' ``` ```json theme={"system"} { "title": "Invalid payment method in selected_payment_method", "status": 400, "code": "invalid_selected_payment_method", "detail": "The payment methods specified in selected_payment_method are invalid. Please re-check.", "instance": "/collect/payment_sessions" } ``` **Rules for `visible_payment_methods`:** * Array of strings. Must be a strict subset of the payment methods enabled for the merchant account. * When not specified, all payment methods enabled for the merchant and available for the session's country and currency are visible. * If only one method is passed, that method is pre-selected and the customer is taken straight into that payment method's journey. * For `pay_by_bank`, target a single bank using `bank_payments.pay_by_bank.{variant}`. See [Bank Variant Reference](#bank-variant-reference) for valid values. **Rules for `selected_payment_method`:** * String. Accepts one value only. * The value must be present in `visible_payment_methods`. * Applies only when `visible_payment_methods` contains more than one entry. ## Match payment methods to the currency The methods you request must be valid for the session's country and currency. For example, a session created in `ZAR` must list methods supported in South Africa. If any method in `visible_payment_methods` is not supported for the session's currency, the request fails with `400 Bad Request`. ## Launch the checkout, handle the webhook, and test Launching the checkout, handling the `payment_session.completed` webhook, verifying the signature, and testing are identical to the standard flow. See [Quickstart: One-Time Payments](/api-reference/collect/payment_sessions/quickstart-one-time) for these steps. # Quickstart: One-Time Payments Source: https://docs.momentco.io/api-reference/collect/payment_sessions/quickstart-one-time Accept your first payment with the Payment Sessions API This guide walks through creating a one-time payment session, launching the hosted checkout, and handling the completion webhook. It also covers how to save a customer's payment method for faster future checkouts. ## Prerequisites * A test API key. See [Authentication](/api-reference/getting-started/authentication). * A registered webhook endpoint. See [Webhook Setup](/api-reference/webhooks/setup) to register your URL and obtain your signing key. *** ## Step 1: Create a payment session Create a session server-side using your **secret key**. The minimum required fields are `amount` and `currency`. ```bash theme={"system"} curl -X POST https://api.momentpay.net/collect/payment_sessions \ -H "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "amount": 10000, "currency": "ZAR" }' ``` `amount` is always specified in minor units. See [Monetary Amounts](/api-reference/getting-started/monetary_amounts) for details. The response includes a `session_url` (the hosted checkout page your customer will use to complete payment) and an `id` you will use to match the subsequent webhook event. ```json theme={"system"} { "id": "ps_kfAWZgfQIoeG0q", "status": "active", "payment_outcome": "not_started", "amount": 10000, "currency": "ZAR", "session_url": "https://checkout.momentpay.net/ckt802zEb2P2uS4Ql", "created_at": "2025-03-18T14:06:51.149Z", "updated_at": "2025-03-18T14:06:51.149Z" } ``` ### Saving a payment method To save the customer's payment method at checkout, add `options.checkout_options.save_payment_method` to the request body. ```json prompt theme={"system"} { "amount": 2001, "currency": "ZAR", "options": { "customer": { "name": "John Doe" }, "checkout_options": { "save_payment_method": "prompt" } } } ``` ```json always theme={"system"} { "amount": 2001, "currency": "ZAR", "options": { "customer": { "name": "John Doe" }, "checkout_options": { "save_payment_method": "always" } } } ``` * **`prompt`** β€” the customer is given the choice to save their payment method at checkout. * **`always`** β€” the payment method is saved automatically with no prompt. Card details are tokenized and stored securely by Moment. Your server never handles or stores raw card numbers. The response has the same shape as above, with one addition β€” a `customer_id` identifying the saved customer. Store this against your own customer record so you can present their saved payment methods on future checkouts. ```json theme={"system"} { "customer_id": "cus_rlpqOZEA9M0PEhrA9ZDdN" } ``` Checkout page showing the save payment method prompt

The customer is prompted with the option to save their card for future checkouts.

*** ## Step 2: Launch the checkout Redirect your customer to the `session_url`, or embed it using the [JavaScript SDK](/libraries-and-sdks/web/javascript) for an in-page modal or inline experience. The session remains `active` until the customer completes payment or the session expires. Do not poll for status. Use the webhook in the next step instead. *** ## Step 3: Handle the completion webhook When the customer completes payment, Moment sends a `payment_session.completed` event to your registered webhook endpoint. ```json theme={"system"} { "id": "wh_e5cf73f1b3e4451c8c474fe56bfc688d", "type": "payment_session.completed", "data": { "id": "ps_kfAWZgfQIoeG0q", "status": "completed", "payment_outcome": "paid", "amount": 10000, "currency": "ZAR", "payment_id": "pay_87C7PAP35KcdpzAgckBvB" } } ``` Match `data.id` in the event to the session you created in Step 1 to confirm which order was paid. Before processing, verify the event signature to confirm it originated from Moment. See [Webhook Verification](/api-reference/webhooks/verification) for the full verification steps and code samples. Respond with a `2xx` status code to acknowledge receipt. If your endpoint does not respond in time, Moment will retry delivery. *** ## Presenting a returning customer with their saved payment method When a customer with a saved payment method returns, pass the saved `customer_id` in the `options.customer` field. Moment will present their saved payment methods at checkout, enabling a faster checkout experience. ```json theme={"system"} { "amount": 2001, "currency": "ZAR", "options": { "customer": "cus_rlpqOZEA9M0PEhrA9ZDdN", "checkout_options": { "save_payment_method": "prompt" } } } ``` You can still include `save_payment_method` for returning customers. Set it to `"prompt"` to give them the option to save a new payment method at checkout. Checkout page showing saved payment methods for a returning customer

The customer is presented with their saved payment methods at checkout.

*** ## Saveable payment methods The following payment methods can be saved for faster checkout on future transactions: * **Cards** β€” Visa, Mastercard, and other supported card schemes * **Capitec Pay** β€” Customers can save their Capitec Pay phone number for quicker checkout without re-entering details *** ## Testing Use test card `4242 4242 4242 4242` with any future expiry date and any 3-digit CVC to simulate a successful payment. See [Integration Testing](/api-reference/getting-started/testing) for the full list of test cards and error scenarios. *** ## Next steps
Set up a mandate and collect future payments without customer interaction. Reserve funds at checkout and capture or void them later. Verify and save a card without charging the customer. Embed checkout directly in your page with the Moment JS SDK.
# Quickstart: Recurring Payments Source: https://docs.momentco.io/api-reference/collect/payment_sessions/quickstart-recurring Accept recurring payments with the Payment Sessions API This guide walks through setting up recurring payments using the Payment Sessions API. The first charge uses a `first_in_series` session to collect payment and save the customer's payment method under a mandate. From there, subsequent charges are either triggered manually with a `next_in_series` session, or collected automatically by adding `subscription_options` to the mandate. ## Prerequisites * A test API key. See [Authentication](/api-reference/getting-started/authentication). * A registered webhook endpoint. See [Webhook Setup](/api-reference/webhooks/setup) to register your URL and obtain your signing key. *** ## Mandate types A mandate is the customer's consent to authorise future charges on their payment method. Creating a `first_in_series` session establishes a mandate that defines the boundaries and constraints for all subsequent payments, whether triggered manually or collected automatically via a subscription. By default, omitting `mandate_options` creates an unconstrained mandate, equivalent to `on_demand` with no declared intent. See [Mandate Type](/documentation/collect/payment-sessions/mandates/consent#mandate-type) for the full semantics of `scheduled`, `on_demand`, and `installment`, and which to use for merchant-triggered vs automated collection. *** ## Supported payment methods The following payment methods support recurring payments: | Payment Method | Countries | | - | - | | **Cards** | ZA, ZM, NG | | **Capitec Pay** | ZA | See [Supported Payment Methods](/documentation/collect/payment-sessions/mandates/supported-payment-methods) for mandate type eligibility and requirements per payment method. *** ## Part 1: Set up the mandate ### Step 1: Create a first\_in\_series session Create the session server-side. The customer must be present to complete this step. They will authorise their payment method for future charges via the hosted checkout. ```bash theme={"system"} curl -X POST https://api.momentpay.net/collect/payment_sessions \ -H "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "first_in_series", "amount": 5000, "currency": "ZAR", "options": { "customer": { "name": "Alex", "email": "alex@example.com" } } }' ``` `amount` is always specified in minor units. See [Monetary Amounts](/api-reference/getting-started/monetary_amounts) for details. To automate collection instead of triggering each payment manually, include `subscription_options` alongside `mandate_options` in this same request. See [Part 2](#part-2-collect-subsequent-payments) below, and [Subscriptions](/documentation/collect/payment-sessions/subscriptions/overview) for the full reference. The response includes a `session_url` for the customer to complete checkout, and a `customer_id` that was created for this customer. ```json theme={"system"} { "id": "ps_eLtQXMBUErQF5i", "type": "first_in_series", "status": "active", "payment_outcome": "not_started", "amount": 5000, "currency": "ZAR", "customer_id": "cus_J17pnG25nuvKu", "session_url": "https://checkout.momentpay.net/cktaoPzVYDN0rI7FH", "created_at": "2025-03-18T14:13:51.379Z", "updated_at": "2025-03-18T14:13:51.379Z" } ``` ### Step 2: Launch the checkout Redirect your customer to the `session_url`. During checkout, the customer completes their payment and authorises future charges to their payment method under the mandate. ### Step 3: Capture the customer and payment method IDs When the customer completes checkout, a `payment_session.completed` event is sent. The `data` object includes both a `customer_id` and a `payment_method_id`. Store both, as you will need them for every subsequent charge. ```json theme={"system"} { "id": "wh_e5cf73f1b3e4451c8c474fe56bfc688d", "type": "payment_session.completed", "data": { "id": "ps_eLtQXMBUErQF5i", "type": "first_in_series", "status": "completed", "payment_outcome": "paid", "amount": 5000, "currency": "ZAR", "customer_id": "cus_J17pnG25nuvKu", "payment_method_id": "pmt_izWG6Vcvy4VYiU", "payment_id": "pay_87C7PAP35KcdpzAgckBvB" } } ``` Verify the event signature before processing. See [Webhook Verification](/api-reference/webhooks/verification). *** ## Part 2: Collect subsequent payments If you included `subscription_options` in [Part 1](#part-1-set-up-the-mandate), each subsequent payment is collected automatically on the schedule you defined; no further action is needed beyond listening for the subscription webhooks below. Otherwise, trigger each payment manually as follows: ### Step 4: Create a next\_in\_series session Use the `customer_id` and `payment_method_id` from [Part 1](#part-1-set-up-the-mandate). The customer is not present and the payment is processed immediately without any checkout step. ```bash theme={"system"} curl -X POST https://api.momentpay.net/collect/payment_sessions \ -H "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "next_in_series", "amount": 5000, "currency": "ZAR", "options": { "customer": "cus_J17pnG25nuvKu", "payment_method": "pmt_izWG6Vcvy4VYiU" } }' ``` There is no `session_url` in the response. The payment processes automatically and a `payment_id` is returned immediately. ```json theme={"system"} { "id": "ps_aWICss5eVG0IWZ", "type": "next_in_series", "status": "active", "payment_outcome": "not_started", "amount": 5000, "currency": "ZAR", "customer_id": "cus_J17pnG25nuvKu", "payment_id": "pay_87C7PAP35KcdpzAgckBvB", "created_at": "2025-03-19T07:18:22.986Z", "updated_at": "2025-03-19T07:18:22.986Z" } ``` ### Step 5: Handle the completion webhook A `payment_session.completed` event is sent once the payment is processed, with the same structure as in [Part 1](#part-1-set-up-the-mandate). Repeat Step 4 for each subsequent charge on the schedule your application manages. If you're using `subscription_options` instead, listen for `subscription.created` (fired once, after the first payment succeeds), and later `subscription.cancelled` or `subscription.ended`. See [Subscription Webhooks](/api-reference/collect/subscriptions/webhook/created). *** ## Testing Use test card `4242 4242 4242 4242` with any future expiry date and any 3-digit CVC for the `first_in_series` checkout. See [Integration Testing](/api-reference/getting-started/testing) for the full list of test cards and error scenarios. *** ## Next steps
Configure consent ceilings, recurrence rules, and validity periods for mandates. Automate the recurring schedule with subscription options. Set up a mandate without charging the customer upfront. Reserve funds at checkout and capture or void them later.
# Retrieve a Payment Session Source: https://docs.momentco.io/api-reference/collect/payment_sessions/retrieve GET /collect/payment_sessions/{id} Retrieve a payment session # Payment Session Completed Source: https://docs.momentco.io/api-reference/collect/payment_sessions/webhook/completed WEBHOOK payment_session.completed The payment session was completed # Payment Session Created Source: https://docs.momentco.io/api-reference/collect/payment_sessions/webhook/created WEBHOOK payment_session.created The payment session was created # Payment Session Expired Source: https://docs.momentco.io/api-reference/collect/payment_sessions/webhook/expired WEBHOOK payment_session.expired The payment session expired due to inactivity or a timeout # Payment Session Updated Source: https://docs.momentco.io/api-reference/collect/payment_sessions/webhook/updated WEBHOOK payment_session.updated The payment session was updated # Capture Payment Source: https://docs.momentco.io/api-reference/collect/payments/capture POST /collect/payments/{id}/captures Captures an authorised payment, settling the funds. This endpoint is only applicable for payments created with `capture_method: manual`. The payment must be in the `succeeded` status (authorised) and the authorisation must not have expired. You can perform a partial capture by specifying an `amount` less than the remaining authorised amount. Omitting the `amount` captures the full remaining authorised amount. # Payment object Source: https://docs.momentco.io/api-reference/collect/payments/object Payment object is used to represent a payment. # Overview Source: https://docs.momentco.io/api-reference/collect/payments/overview The **Payments API** is a flexible building block that gives merchants full control over the payment flow. It is ideal for creating fully customized payment experiences that go beyond prebuilt pages or links. ## Authorisation-Only Payments By setting `capture_method: manual` when creating a payment session, you can authorise a payment without immediately capturing the funds. This is useful for scenarios where you need to verify a customer's payment method and reserve funds before fulfilling an order. When `capture_method` is set to `manual`: * A successful payment transitions to the `succeeded` status, indicating that funds have been **authorised** (reserved) but not yet settled. * Use the [Capture Payment](/api-reference/collect/payments/capture) endpoint to settle the funds. * Use the [Void Payment](/api-reference/collect/payments/void) endpoint to release the reserved funds. * Partial captures are supported. Voids always release the full remaining authorised amount. * Authorisations expire after a set period determined by the issuing bank and card network. After expiry, the authorisation can no longer be captured. For a detailed guide on the authorisation and capture lifecycle, see [Authorisation & Capture](/documentation/collect/payment-sessions/authorisation-capture/overview). ## Key Concepts | Field | Type | Description | | - | - | - | | `capture_method` | `string` | `automatic` (default) settles immediately on authorisation. `manual` reserves funds without settling, enabling the capture/void flow. | | `authorised_amount` | `integer \| null` | The original total amount authorised on the customer's payment method, in minor units. Remains stable across capture and void operations. Present only when the payment method supports authorisation. | | `paid_amount` | `integer \| null` | Total amount captured (settled), in minor units. Incremented when a capture is performed. | | `voided_amount` | `integer \| null` | Total amount released from the original authorisation, in minor units. Incremented when a void is performed. Present only when the payment method supports authorisation. | When `capture_method` is `manual`, the remaining capturable amount is `authorised_amount - paid_amount - voided_amount`. ## **Webhooks** The **Payments API** supports webhook events that allow you to monitor and react to changes in payment state in real time. Webhook events can be used to update internal systems, trigger order fulfillment, or log payment outcomes for reporting and reconciliation. ### **Available Events** * **`payment.succeeded`** Triggered when a payment is successfully completed. πŸ‘‰ [See `payment.succeeded` Event](/api-reference/collect/payments/webhook/succeeded) * **`payment.attempt_failed`** Triggered when an attempt is failed. πŸ‘‰ [See `payment.attempt_failed` Event](/api-reference/collect/payments/webhook/attempt_failed) * **`payment.failed`** Triggered when all payment attempts are exhausted and the payment has failed terminally. πŸ‘‰ [See `payment.failed` Event](/api-reference/collect/payments/webhook/failed) * **`payment.captured`** Triggered when an authorised payment is successfully captured (fully or partially). πŸ‘‰ [See `payment.captured` Event](/api-reference/collect/payments/webhook/captured) * **`payment.voided`** Triggered when an authorised payment is successfully voided. πŸ‘‰ [See `payment.voided` Event](/api-reference/collect/payments/webhook/voided) The event payload includes a `payment` object, which contains fields such as `created_at`, `updated_at`, and `payment_method_details`. The `payment_method_details` field includes the payment method `type` (e.g., `card`) and additional metadata like `scheme` and `bin`. ### Error Codes πŸ‘‰ [See Common Error Codes](/api-reference/getting-started/errors) # Retrieve a Payment Source: https://docs.momentco.io/api-reference/collect/payments/retrieve GET /collect/payments/{id} Retrieve a payment # Void Payment Source: https://docs.momentco.io/api-reference/collect/payments/void POST /collect/payments/{id}/voids Voids an authorised payment, releasing the full remaining reserved funds back to the customer. This endpoint is only applicable for payments created with `capture_method: manual`. The payment must be in the `succeeded` status (authorised) and must have remaining authorised amount. The void always releases the full remaining authorised amount (`authorised_amount - paid_amount - voided_amount`). Partial voids are not supported. # Payment Attempt Failed Source: https://docs.momentco.io/api-reference/collect/payments/webhook/attempt_failed WEBHOOK payment.attempt_failed The payment attempt failed. # Payment Captured Source: https://docs.momentco.io/api-reference/collect/payments/webhook/captured WEBHOOK payment.captured Triggered when an authorised payment is successfully captured (fully or partially). # Payment Expired Source: https://docs.momentco.io/api-reference/collect/payments/webhook/expired WEBHOOK payment.expired Triggered when a payment expires before it could be completed. If the payment expired without any payment attempt being made, `payment_method_details` is not present in the payload. # Payment Failed Source: https://docs.momentco.io/api-reference/collect/payments/webhook/failed WEBHOOK payment.failed Triggered when all payment attempts are exhausted and the payment has failed terminally. # Payment Succeeded Source: https://docs.momentco.io/api-reference/collect/payments/webhook/succeeded WEBHOOK payment.succeeded The payment succeeded. # Payment Voided Source: https://docs.momentco.io/api-reference/collect/payments/webhook/voided WEBHOOK payment.voided Triggered when an authorised payment is successfully voided. `authorised_amount` remains the original authorised total while `voided_amount` reflects the amount released. # Confirm a Redemption Source: https://docs.momentco.io/api-reference/collect/redemptions/confirm POST /collect/redemptions/{id}/confirm Calls the provider's confirm operation. Transitions `reserved` to `confirmed` on success, `failed` otherwise. # Create a Redemption Source: https://docs.momentco.io/api-reference/collect/redemptions/create POST /collect/redemptions Creates a redemption. Final status depends on the selected provider's hold capability. # Fetch All Redemption Types Source: https://docs.momentco.io/api-reference/collect/redemptions/get-types GET /collect/redemptions/types Returns the redemption types the API supports. # Redemption object Source: https://docs.momentco.io/api-reference/collect/redemptions/object Redemption object is used to represent a redemption. # Overview Source: https://docs.momentco.io/api-reference/collect/redemptions/overview The **Redemptions API** is a stateful building block that orchestrates voucher redemption flows against external providers (1Voucher, OTT, Blu). It gives merchants full control over the lifecycle (reserve funds, then confirm consumption) through a consistent interface that abstracts provider variances. To check a balance before redeeming, use the [Stored Value API](/api-reference/collect/stored-values/overview). πŸ‘‰ [See Product Guide](/documentation/collect/redemptions/overview) ## **Webhooks** The Redemption API supports webhook events that allow you to track the lifecycle and state changes of a redemption in real time. These webhooks can be used to automate internal processes, trigger notifications, or initiate subsequent redemption flows. ### **Available Events** * **`redemption.created`** Fired when a new redemption is created (status `draft`). πŸ‘‰ [See `redemption.created` Event](/api-reference/collect/redemptions/webhook/created) * **`redemption.confirmed`** Fired when the redemption is successfully confirmed from the provider and the funds are consumed. πŸ‘‰ [See `redemption.confirmed` Event](/api-reference/collect/redemptions/webhook/confirmed) * **`redemption.failed`** Fired whenever a state transition lands in a `failed` outcome. πŸ‘‰ [See `redemption.failed` Event](/api-reference/collect/redemptions/webhook/failed) ### Error Codes πŸ‘‰ [See Common Error Codes](/api-reference/getting-started/errors) # Quickstart: Voucher Redemption Source: https://docs.momentco.io/api-reference/collect/redemptions/quickstart-voucher-redemption Redeem a voucher for the full amount with the Redemptions API This guide walks through headless voucher redemption for the full amount and handling the redemption confirmation webhook. ## Prerequisites * A test API key. See [Authentication](/api-reference/getting-started/authentication). * A registered webhook endpoint. See [Webhook Setup](/api-reference/webhooks/setup) to register your URL and obtain your signing key. *** ## Step 1: Create a redemption Create a redemption server side using your **secret key**. The minimum required fields are `type` and `source.pin` or `source.token`. Conform your UI to the required `source` attributes per `type`. See [the `source` schema](/api-reference/collect/redemptions/object#schema-source). `amount` and `currency` are optional. Omit both to redeem the voucher's full value. The example below sets an explicit `amount`; remove the `amount` and `currency` fields to redeem the full value instead. ```bash theme={"system"} curl --request POST \ --url https://api.momentpay.net/collect/redemptions \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --data ' { "type": "vouchers.one_voucher", "source": { "pin": "1115684091419838" }, "amount": 1900, "currency": "ZAR", "external_reference": "order-2026-0001" } ' ``` The response includes a status of `confirmed` and the `redeemed_amount`. ```json theme={"system"} { "id": "rdm_6a0d5c7fe182228a6c64cb78", "status": "confirmed", "version": 3, "type": "vouchers.one_voucher", "provider_reference": { "serial_number": "20260520082514335162" }, "amount": 1900, "reserved_amount": 1900, "redeemed_amount": 1900, "reversed_amount": 0, "currency": "ZAR", "operations": [ { "id": "mpd2156u_7cwvqp5m", "type": "reserve", "status": "succeeded", "amount": 1900, "provider_reference": { "pin": "1115684091419838" }, "created_at": "2026-05-20T08:25:01.014Z" }, { "id": "mpd2185c_vz0wyx10", "type": "confirm", "status": "succeeded", "amount": 1900, "provider_reference": { "serial_number": "20260520082514335162" }, "created_at": "2026-05-20T08:25:14.248Z" } ], "related_ids": [], "external_reference": "order-2026-0001", "created_at": "2026-05-20T08:24:56.501Z", "updated_at": "2026-05-20T08:25:14.249Z" } ``` *** ## Testing Use the following 16 digit `source.pin` (or `source.token`) to test voucher redemption. The following values are redeemable with all three providers for the indicated amount. Any `source.pin` besides the values below results in a failure. | `source.pin` or `source.token` | status | redeemable.amount | | - | - | - | | 12345678901234 | `confirmed` | | | 99999999999999 | `confirmed` | | | 88888888888888 | `confirmed` | | | 77777777777777 | `confirmed` | 10000 | | anything else | `failed` | | Use `77777777777777` to test partial redemption. It carries a redeemable balance of `10000` (minor units). Requesting an `amount` lower than `10000` settles the redemption as confirmed and leaves the remaining value, returned as a change voucher where the provider supports it. See [Providers](/documentation/collect/redemptions/providers) for provider support. *** ## Next steps * Handle the [`redemption.confirmed` webhook](/api-reference/collect/redemptions/webhook/confirmed) to react to the confirmed redemption server side. * Review the full [Redemption object](/api-reference/collect/redemptions/object). # Retrieve a Redemption Source: https://docs.momentco.io/api-reference/collect/redemptions/retrieve GET /collect/redemptions/{id} # Redemption Confirmed Source: https://docs.momentco.io/api-reference/collect/redemptions/webhook/confirmed WEBHOOK redemption.confirmed Fired when the redemption is successfully confirmed from the provider and the funds are consumed. # Redemption Created Source: https://docs.momentco.io/api-reference/collect/redemptions/webhook/created WEBHOOK redemption.created Fired when a new redemption is created (status `draft`). # Redemption Failed Source: https://docs.momentco.io/api-reference/collect/redemptions/webhook/failed WEBHOOK redemption.failed Fired whenever a state transition lands in a failed outcome. # Balance Lookup Source: https://docs.momentco.io/api-reference/collect/stored-values/lookup POST /stored_value/lookup Balance enquiry against a provider's stored value catalog. # Overview Source: https://docs.momentco.io/api-reference/collect/stored-values/overview Stored value lookups Stored value endpoints let you query a provider for the balance and metadata of a stored value instrument, such as a voucher, without creating a redemption. Stateless balance and metadata query against a provider. # Subscriptions object Source: https://docs.momentco.io/api-reference/collect/subscriptions/object Subscriptions object is used to represent a subscription. # Overview Source: https://docs.momentco.io/api-reference/collect/subscriptions/overview Automatic recurring payment collection built on top of the Payment Sessions API. Subscriptions enable automatic payment collection on a recurring schedule. They are created through the [Payment Sessions API](/api-reference/collect/payment_sessions/overview) when `subscription_options` are passed during a `first_in_series` payment session. When the first payment succeeds, it acts as an authorisation and sets up the underlying mandates required for future charges. The subscription is then automatically activated and payments are collected according to the `recurrence` parameters defined at setup. ## How it works 1. Create a `first_in_series` payment session with `subscription_options` including the recurrence configuration. 2. The customer completes the first payment, authorising future charges. 3. Moment sets up the required mandates and activates the subscription. 4. Subsequent payments are collected automatically on the schedule defined by the `recurrence` parameters. ## Webhooks The Subscriptions API emits events to notify you of changes to a subscription's lifecycle. | Event | Description | | - | - | | [`subscription.created`](/api-reference/collect/subscriptions/webhook/created) | Fired when a subscription is created and activated after a successful first payment. | | [`subscription.charge_succeeded`](/api-reference/collect/subscriptions/webhook/charge_succeeded) | Fired when a scheduled charge for a subscription is successfully collected. | | [`subscription.charge_failed`](/api-reference/collect/subscriptions/webhook/charge_failed) | Fired when a scheduled charge for a subscription fails to be collected. | | [`subscription.ended`](/api-reference/collect/subscriptions/webhook/ended) | Fired when a subscription reaches its end date and all scheduled payments have been collected. | | [`subscription.cancelled`](/api-reference/collect/subscriptions/webhook/cancelled) | Fired when a subscription is cancelled before reaching its end date. | # Subscription Cancelled Source: https://docs.momentco.io/api-reference/collect/subscriptions/webhook/cancelled WEBHOOK subscription.cancelled Sent when a subscription is cancelled before reaching its end date. # Subscription Charge Failed Source: https://docs.momentco.io/api-reference/collect/subscriptions/webhook/charge_failed WEBHOOK subscription.charge_failed Sent when a scheduled charge for a subscription fails to be collected. # Subscription Charge Succeeded Source: https://docs.momentco.io/api-reference/collect/subscriptions/webhook/charge_succeeded WEBHOOK subscription.charge_succeeded Sent when a scheduled charge for a subscription is successfully collected. # Subscription Created Source: https://docs.momentco.io/api-reference/collect/subscriptions/webhook/created WEBHOOK subscription.created Sent when a new subscription is created. # Subscription Ended Source: https://docs.momentco.io/api-reference/collect/subscriptions/webhook/ended WEBHOOK subscription.ended Sent when a subscription reaches its end date and all scheduled payments have been collected. # Authentication Source: https://docs.momentco.io/api-reference/getting-started/authentication Learn how the API ensures secure access API keys are used to authenticate requests, typically provided as an `Authorization` header. Your keys are used to identify the account on our platform, and carries many privileges, so be sure to keep them secure! Do not share your secret API keys in publicly accessible areas such as public source code repositories, client-side code, and so forth. All API requests must be made over HTTPS. Calls made over plain HTTP will fail. API requests: * without authentication will return an unauthorized error * without a valid key will return an invalid request error * with a deleted or expired key will return a forbidden error #### Operating Modes The Moment APIs support two modes of operation: **Test Mode** and **Live Mode**.\ Each mode is associated with a unique pair of API keys β€” a **Secret Key** and a **Public Key** β€” resulting in a total of four keys per account. 1. **Live Mode** – In this mode, real funds are transferred. Use it only in production environments. 2. **Test Mode** – No actual funds are transferred in this mode. It is intended solely for development, testing, and integration purposes. | | Test Mode | Live Mode | | - | - | - | | **Prefixes** | `sk_test_*********************`, `pk_test_*********************` | `sk_*********************`, `pk_*********************` | | **Purpose** | Used for integration and testing | Used for live API requests in production | #### API Keys Every API request must include the `Authorization` header with your API key as a bearer token: ```http theme={"system"} Authorization: Bearer ```
The API key will be either a **secret key** or a **public key**, as described below: | | Secret Key | Public Key | | - | - | - | | **Format** | sk\_\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* | pk\_\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* | | **Purpose** | Intended for **server-side interactions**. | Intended for **client-side interactions**. | | **Scope** | Provides full access to the API, including the ability to create, read, update, and delete resources. | Limited access for operations that do not expose sensitive data or require elevated permissions (e.g., creating payment sessions). | | **Security** | Keep this key private, use it for server-to-server API calls, and do not expose it in client-side applications like web browsers or mobile apps. | Safe for use in client-side applications but should still be restricted to specific operations. | | **Example** | sk\_vYZCAeHawUqDCmVpEKh2U | pk\_WeHsNn87r4gtNc5BQT9Mj |
To keep your account safe, we recommend you: * Rotate keys regularly to enhance security. * Revoke compromised keys immediately.
New keys can be requested from the [customer success](/documentation/support) team as needed. #### Business Units An API key is issued against your organisation and is linked to one or more [business units](/guides/company-structure). The business unit determines which processing account a request belongs to, and every transaction and settlement is scoped to it. If your key is linked to **more than one** business unit, every request must name the one it applies to using the `Moment-Business-Unit` header: ```http theme={"system"} Authorization: Bearer Moment-Business-Unit: bu_Jc1Ami3OFuKMXPMjp90HF ``` If your key is linked to exactly **one** business unit, the header can be omitted and that business unit is used. Business unit identifiers are prefixed with `bu_` and can be found in the Client Portal. Requests that omit the header on a multi business unit key, or that send an identifier the key is not linked to, are rejected. See [business unit errors](/api-reference/getting-started/errors#business-unit-errors) for the exact responses. The header selects which business unit to act on. It does not grant access. A key can only ever act on the business units it is already linked to. # Errors Source: https://docs.momentco.io/api-reference/getting-started/errors Learn how the API handles errors This guide outlines how our API handles errors, providing consistent, structured information to developers to help them diagnose and respond to failures effectively. ## HTTP Error codes We follow industry standards for HTTP status codes and enrich them with detailed error information using the [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457.html) Problem Details for HTTP APIs specification. ### Standard Status Codes In general, codes in the 2xx range indicate success. Codes in the 4xx range indicate an error that failed given the information provided (e.g., a required parameter was omitted, etc.). Codes in the 5xx range indicate an error with our servers (these are rare). | Code | Description | Reason | | - | - | - | | 400 | Bad Request | The request was unacceptable, often due to missing a required parameter. | | 401 | Unauthorised | No valid API key provided. | | 402 | Request Failed | The parameters were valid but the request failed. | | 403 | Forbidden | The API key doesn’t have permissions to perform the request. | | 404 | Not Found | The requested resources doesn't exist. | | 409 | Conflict | The request conflicts with another request (perhaps due to using the same idempotent key). | | 422 | Unprocessable Content | The parameters were valid but the request could not be processed. | | 429 | Too Many Requests | Too many requests hit the API too quickly. We recommend an exponential backoff of your requests. | | 500 | Internal Server Errors | A generic error occurred on our side. | | 502, 503, 504 | Gateway/Timeout Errors | Issues communicating with external dependencies or internal services. | ### Problem Details When an error occurs, we return a JSON response conforming to [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457.html). For example, a typical error response might look like this: ```json theme={"system"} { "type": "https://api.momentco.io/docs/errors#not-found", "title": "The requested resource was not found", "status": 404, "detail": "The requested resources doesn't exist", "instance": "/collect/payment_sessions/ps_36dED4m12MLxn48BUrn4R" } ``` This standard ensures that each error response includes a consistent set of fields, such as: * `type`: A URI identifier for the error type. * `title`: A short, human-readable summary of the problem. * `status`: The HTTP status code. * `detail`: A human-readable explanation specific to the occurrence. * `instance`: A unique identifier for the error occurrence (can be used in support/debugging). These fields help developers quickly understand the nature of the error and how to resolve it. ### Business unit errors If your API key is linked to more than one [business unit](/guides/company-structure), every request must identify the target business unit with the `Moment-Business-Unit` header. A key linked to a single business unit can omit the header. ```json theme={"system"} { "type": "https://api.momentco.io/docs/errors#business-unit-missing", "title": "Missing business unit", "status": 400, "code": "business_unit_missing", "detail": "This API key is linked to multiple business units. Provide the Moment-Business-Unit header to select one.", "instance": "/collect/payment_sessions" } ``` | Error Code | Status | Description | | - | - | - | | `business_unit_missing` | 400 | The header was not sent and the API key is linked to more than one business unit. | | `business_unit_invalid` | 400 | The header value is not a valid business unit identifier. Identifiers are prefixed with `bu_`. | | `business_unit_access_denied` | 403 | The header value is a valid identifier, but the API key is not linked to that business unit. | A `403 business_unit_access_denied` does not mean the API key is invalid. The key authenticated successfully, so rotating it does not help. Check that the business unit is one the key is entitled to use. ## Application Error Codes In addition to HTTP status codes, we provide application-level error codes for granular insight into what went wrong during a payment operation. ### Error Object When a payment attempt fails, the failure is recorded in the [last\_payment\_error](/api-reference/collect/payment_sessions/object#schema-last-payment-error) field of the response. This object is included in payment related responses and has the following structure: ```json theme={"system"} { "error_code": "card_declined", "decline_code": "insufficient_funds", "message": "The card has insufficient funds.", "timestamp": "2025-06-03T12:34:56Z" } ``` The fields are: * `error_code`: A general error code indicating the high-level reason for failure, (e.g. `card_declined`). See [Error Codes](/api-reference/getting-started/errors#error-codes). * `decline_code`: A more specific code that explains why a card transaction was declined, typically provided by the issuing bank, payment processor, or credit card network. (e.g. `insufficient_funds`). See [Decline Codes](/api-reference/getting-started/errors#decline-codes). * `message`: A human-readable message describing the error. * `timestamp`: An ISO 8601 timestamp indicating when the error occurred. ### Error Codes A general error code indicating the high-level reason for failure. The following are the possible values for the `code` field, along with their descriptions: | Error Code | Description | | - | - | | `account_invalid` | The request tried to create a payment, but the account number was invalid. The request was rejected. Corrective action: Advise the customer to check the account number. | | `amount_beyond_limit` | The payment cannot be completed with the provided amount, please try again with a different amount. | | `card_declined` | The payment could not be completed with this card. Please check decline\_code for more details. | | `insufficient_funds` | The card does not have enough funds. | ### Mandate Error Codes When processing payment sessions, mandates enable recurring or series-based payments. A mandate is created during a `first_in_series` payment by passing a `mandate_options` block, which defines the consent taken from the customer. This mandate is then associated with the customer's payment method and enforced on subsequent `next_in_series` payments. If a mandate validation fails during a `next_in_series` payment, the API returns a synchronous error response in the standard RFC 9457 format: ```json theme={"system"} { "title": "Mandate Amount Out of Range", "status": 400, "code": "mandate_amount_out_of_range", "detail": "The payment amount falls outside the range allowed by the mandate", "instance": "/collect/payment_sessions" } ``` The following mandate error codes may be returned: | Error Code | Description | | - | - | | `mandate_not_found` | The specified mandate could not be found. | | `invalid_mandate_status` | The mandate is not in a valid status for this operation. | | `mandate_expired` | The mandate has expired and can no longer be used. | | `mandate_not_yet_started` | The mandate has not yet reached its start date. | | `mandate_max_payments_reached` | The maximum number of payments allowed by the mandate has been reached. | | `mandate_already_used` | The mandate has already been used and cannot be reused. | | `mandate_amount_out_of_range` | The payment amount falls outside the range allowed by the mandate. | | `mandate_amount_exceeds_limit` | The payment amount exceeds the limit set by the mandate. | | `payment_not_eligible_for_mandate_creation` | The payment is not eligible for creating a mandate. | ### Decline Codes A more specific code that explains why a card transaction was declined, typically provided by the issuing bank, payment processor, or credit card network. The following are the possible values for the `decline_code` field, along with their descriptions and recommended rectification actions: | Decline Code | Description | Rectification Action | | - | - | - | | `account_closed` | The cardholder’s account has been closed. | Use a different payment method. | | `additional_customer_authentication_required` | The card was declined because the transaction requires authentication. | The customer should try again and authenticate their card when prompted during the transaction. | | `blocked_first_use` | The transaction was blocked by the issuer. | The customer needs to contact their card issuer to resolve the issue. | | `cannot_verify_pin` | The transaction requires PIN but it couldn't be verified. | The customer needs to try again by inserting their card and entering a PIN. | | `card_expired` | The card has expired. | The customer needs to use a valid, non-expired card. | | `card_not_found` | The card was not found in the records of the issuer. | The customer needs to contact their card issuer to resolve the issue. | | `customer_canceled` | The customer has canceled the transaction or recurring payment. | Confirm the customer’s intent or request a different payment method. | | `cvv_not_valid` | The customer entered an incorrect CVV code. | The customer needs to re-enter the correct CVV code. | | `do_not_honor` | The card issuer has declined the transaction without a specific reason. | The customer needs to contact their card issuer for more information. | | `generic_decline` | The card issuer declined the transaction for an unspecified reason. | The customer needs to contact their card issuer or try a different card. | | `geo_restricted` | The transaction was declined due to geographic restrictions by the issuer. | The customer needs to contact their issuer or use a different payment method. | | `incorrect_pin` | The PIN entered is incorrect. | The customer needs to try again with the correct PIN. | | `insufficient_funds` | The card has insufficient funds to complete the transaction. | The customer needs to add funds or use a different card. | | `invalid_account_type` | The card, or account the card is connected to is invalid. | The customer needs to contact their card issuer to resolve the issue. | | `invalid_amount` | The payment amount is invalid, or exceeds the amount that's allowed. | If the amount appears to be correct, the customer needs to check with the issuer that they can make purchases of that amount. | | `invalid_card_number` | The card number is incorrect. | The customer needs to try again using the correct card number. | | `invalid_merchant` | The merchant was not recognized by the payment processor. | Reach out to the payment processor to resolve the issue. | | `invalid_transaction` | The transaction was rejected by the issuer. | The merchant needs to contact the payment processor to resolve the issue. | | `issuer_temporarily_unavailable` | The card issuer couldn't be reached, so the payment couldn't be authorized. | Attempt the payment again. If you still can't process it, the customer needs to contact the issuer. | | `issuer_unavailable` | The card issuer couldn't be reached, so the payment couldn't be authorized. | The customer needs to contact their card issuer to resolve the issue. | | `limit_exceeded` | The transaction exceeds the card’s credit or withdrawal limit. | The customer needs to use a different card or wait until the limit resets. | | `lost_card` | The card has been reported as lost. | Use an alternate payment method. | | `no_action_taken` | The transaction was attempted but no action was taken. | The customer should retry the transaction. | | `no_reason` | The card was declined for an unknown reason. | The customer needs to contact their card issuer to resolve the issue. | | `partial_approval` | The transaction could not be fully completed as authorized. | The customer needs to contact their issuer or use a different payment method. | | `pickup_card` | The customer can’t use this card to make this payment (it’s possible it was reported lost or stolen). | The customer needs to contact their issuer or use a different payment method. | | `pin_data_required` | The card was declined because it requires a PIN. | The customer needs to try again by inserting their card and entering a PIN. | | `pin_retries_exceeded` | The allowable number of PIN tries was exceeded. | The customer must use another card or method of payment. | | `recurring_charge_stopped_by_customer` | Your customer asked their bank to deny charges from your account. | Stop the recurring payments immediately. | | `refer_to_issuer` | The card issuer declined the transaction for an unspecified reason. | The customer needs to contact their issuer or use a different payment method. | | `retry_transaction` | The payment couldn't be processed by the issuer for an unknown reason. | The payment needs to be attempted again. If it still can't be processed, the customer needs to contact their card issuer. | | `security_violation` | The card was declined for an unknown reason. | The customer needs to contact their issuer or use a different payment method. | | `stolen_card` | The card has been reported as stolen. | Use an alternate payment method. | | `suspected_fraud` | The transaction was flagged as suspicious by the issuer. | The customer needs to contact their card issuer to resolve the issue. | | `system_error` | A temporary error occurred during the transaction. | Wait a minute or two and try again. | | `transaction_failed_aml_check` | The transaction was flagged for suspicion of money laundering by the issuer. | The customer needs to contact their card issuer to resolve the issue. | | `transaction_not_permitted` | The payment is not permitted. | The customer needs to contact their card issuer to resolve the issue. | | `unable_to_route_transaction` | The payment could not be routed to the issuer due to network or system errors on the issuers side. | The transaction can be retried, the customer needs to contact the issuer to resolve the issue if the issue persists | | `unknown_issuer` | The card issuer is not recognized or doesn't exist. | The customer needs to contact their issuer or use a different payment method. | | `unsolicited_reversal` | A reversal was initiated without a prior request or authorization from the merchant. | The merchant needs to contact the customer to resolve the issue. | ## Best Practices for Clients To ensure reliable and developer-friendly integrations, we recommend the following practices when handling errors from our API: * Always check the HTTP status code before attempting to parse the response body. Use it to determine the broad category of error (e.g., client-side vs. server-side). * Use the `error_code` field for internal application logic. This field is stable and maps to general error categories (e.g., card\_declined). It’s ideal for branching error-handling flows or conditional retry logic. * Use the `message` field for human-readable error messages that can be logged or displayed in dashboards. This message is designed to be developer-friendly but not always end-user appropriate. * Use the `decline_code` field for additional context, especially for card-related failures. These codes represent issuer-specific reasons for declines and are useful for analytics, support tooling, or to tailor customer-facing messages when applicable. * Log the full `last_payment_error` object on failed payment attempts for easier debugging and operational visibility. * Retry only when appropriate: * Retry on `5xx` errors with backoff. * Do not retry on `4xx` errors unless explicitly documented as safe to do so (e.g. when rate limited). * Monitor for `402 - Request Failed` errors to detect cases where the request has been accepted but the state of the object may indicate a processing error, for example a payment has been created, but the payment processing may still have failed. * Monitor for `422 - Unprocessable Content` errors to detect integration issues, such as invalid payment state or business rule violations. # Idempotency Source: https://docs.momentco.io/api-reference/getting-started/idempotency Learn how the API prevents duplicate requests An API call or operation is idempotent if it always produces the same outcome, regardless of how many times it is executed. This ensures that accidental duplicate calls do not cause unintended consequences. Our API implements **idempotency** as per the [RFC Draft Specification for Idempotency](https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-idempotency-key-header). We enable idempotency by allowing API operations to include an idempotency key as a header in the request. The key is a unique string created by the client and is used by the server to recognise subsequent retries of the same request. It acts as a safeguard against accidental duplicate calls that could lead to undesired outcomes. The method for creating unique idempotency keys is flexible, but we recommend using UUIDs or other randomly generated strings. To execute an idempotent request, include an additional element called `Idempotency-Key` in the request headers of any POST or PUT requests. ```http theme={"system"} Idempotency-Key: 2f227800-f0c9-4000-a798-ca2e8b796dcc ``` Idempotency works by saving the resulting status code and body of the first request made for any given idempotency key, regardless of whether it succeeded or failed. Subsequent requests with the same key return the same result, status code and a response header of `Idempotent-Replayed` set to `true`. Keys are removed from the system automatically after 24 hours. A subsequent request with the same key after its expiry period, will be treated as a new request. # Metadata Source: https://docs.momentco.io/api-reference/getting-started/metadata Learn how to pass additional information using Metadata ### Metadata The `metadata` field is a flexible attribute available on select objects, allowing merchants to attach custom key-value pairs for internal use. This is particularly useful for associating additional contextual informationβ€”such as an order number or customer reference IDβ€”to streamline backend operations and integrations. > **Note:** Moment does not use metadata for any business logic. This field is solely intended for merchants to pass supplementary information. #### Usage Restrictions Each `metadata` object is subject to the following constraints: * **Maximum number of key-value pairs:** 20 * **Key**: Minimum 1 character, maximum 50 characters * **Value**: Minimum 1 character, maximum 500 characters * **Data type**: Both `key` and `value` must be strings Do not include any sensitive information in metadata, such as credit card numbers, bank account details, or personally identifiable information (PII). *** # Monetary Amounts Source: https://docs.momentco.io/api-reference/getting-started/monetary_amounts Learn how the API works with monetary amounts When making API requests, monetary amounts must include a currency code and a value. The value must be specified in minor units, based on the currency's decimal places. This page lists supported currency codes and explains minor units. ## Currency Code Explained Currencies are specified using three-letter currency codes that follow the [ISO 4217 Alpha-3](https://www.iso.org/iso-4217-currency-codes.html) standard. The number of decimals determines how to express the currency amounts in minor units. ### Supported currencies | **Currency Code** | **Symbol** | **Currency Name** | **Decimal Units** | | - | - | - | - | | EUR | € | Euro | 2 | | GBP | Β£ | British Pound | 2 | | KES | KSh | Kenyan Shilling | 2 | | NGN | ₦ | Nigerian Naira | 2 | | TZS | TSh | Tanzanian Shilling | 2 | | UGX | USh | Ugandan Shilling | 0 | | USD | \$ | US Dollar | 2 | | ZAR | R | South African Rand | 2 | ## Minor Units Explained Monetary amounts must be provided in minor units β€” the smallest unit of a currency, such as cents or pennies. The number of minor units depends on the currency’s decimal places. For example, ZAR 10.95 is represented as 1095 (in cents). #### Understanding Minor Units * **Most currencies** use **two decimal places** (e.g., ZAR, USD, GBP), meaning you would multiply the currency amount by 100 to get the value in minor units. * **Some currencies** have **no decimal places** (e.g., JPY). * **A few currencies** use **three decimal places** (e.g., BHD). ## Examples The table below illustrates how minor units work across different decimal place conventions. Currencies not in the [supported currencies](#supported-currencies) list are included for illustration only. | **Currency** | **Decimal Places** | **Amount (Major Units)** | **Amount (Minor Units)** | | - | - | - | - | | **BHD** (Bahraini Dinar) | 3 | 10.000 BHD | 10000 fils | | **EUR** (Euro) | 2 | 10.00 EUR | 1000 cents | | **GBP** (British Pound) | 2 | 10.00 GBP | 1000 pence | | **JPY** (Japanese Yen) | 0 | 10 JPY | 10 yen | | **KES** (Kenyan Shilling) | 2 | 10.00 KES | 1000 cents | | **NGN** (Nigerian Naira) | 2 | 10.00 NGN | 1000 kobo | | **TZS** (Tanzanian Shilling) | 2 | 10.00 TZS | 1000 cents | | **UGX** (Ugandan Shilling) | 0 | 10 UGX | 10 shillings | | **USD** (US Dollar) | 2 | 10.00 USD | 1000 cents | | **ZAR** (South African Rand) | 2 | 10.00 ZAR | 1000 cents | This format ensures consistency when processing payments across different currencies. # Overview Source: https://docs.momentco.io/api-reference/getting-started/overview API reference for the Moment platform Everything you need to integrate with Moment, from API authentication and error handling to product-specific endpoints and webhooks. Get your API key and webhook endpoint set up, then pick your integration path. *** ## Foundational Concepts
How to authenticate requests using API keys. How to safely retry requests without creating duplicates. Error response structure and common error codes. How amounts and currencies are represented in the API. Attach custom key-value data to any resource.
*** ## APIs
Payment Sessions, Payment Links, and Payments APIs. Customers, Accounts, and Bills APIs. Event notifications for all Moment resources.
*** ## Technical Reference
Simulate payments and test your integration before going live. Track marketing campaign attributes using URL parameters on bill presentment.
# Quickstart Source: https://docs.momentco.io/api-reference/getting-started/quickstart Everything you need before making your first API call Before integrating with any Moment API, you need two things: an API key and a webhook endpoint. This page covers both, then points you to a product-specific quickstart. ## Step 1: Get your API key All requests are authenticated using a Bearer token. Use your **secret key** for server-side calls and your **public key** for client-side operations. ```http theme={"system"} Authorization: Bearer sk_test_YOUR_SECRET_KEY ``` Moment provides separate key pairs for **test** and **live** modes. Use test keys during development β€” no real funds move. See [Authentication](/api-reference/getting-started/authentication) for full details on key types and security practices. *** ## Step 2: Register a webhook endpoint Moment uses webhooks to notify your server when events occur, such as a payment completing or a bill being paid. You need a registered endpoint before you can receive these events. Contact the [support team](/documentation/support) to register your endpoint URL and receive your signing key. Use [ngrok](https://ngrok.com) or a similar tunnel during local development. See [Webhook Setup](/api-reference/webhooks/setup) for request headers, retry behaviour, and event structure. *** ## Step 3: Pick your integration
Accept one-time and recurring payments using Payment Sessions. Manage customer balances, accounts, and bills to collect payments.
# Integration Testing Source: https://docs.momentco.io/api-reference/getting-started/testing This guide will teach how to test your integration for different scenarios when integrating with Moment. ### Overview To ensure your integration with Moment APIs functions correctly, you can simulate payment flows during development. This allows you to test various scenarios without any real money movement. ## Testing Cards When testing card payments, always use [test API keys](/api-reference/getting-started/authentication#operating-modes). Only use the specified test cards listed below. **Do not use real card** information under any circumstances. > Note: The 3DS challenge page will not be displayed during testing, as the flow is being simulated to mimic a frictionless authentication experience. ### Supported card test scenarios βœ… **Success scenarios** | Scenario | Test Card Number | CVC | Pin | Expiry Date | Notes | | :- | :- | :- | - | :- | :- | | Valid Test Visa card | 4242 4242 4242 4242 | Any 3 digit | - | Any future date | You receive `12/35` as expiry date | | Valid Test Master card | 5555 5555 5555 4444 | Any 3 digit | - | Any future date | You receive `12/35` as expiry date | | Valid Test Verve card | 5060 9900 0008 4242 | Any 3 digit | Any 4 digit | Any future date | You receive `12/35` as expiry date | ❌ **Error scenarios** | Scenario | Test Card Number | Error Code | Decline Code | Notes | | :- | :- | :- | :- | :- | | The card was declined by the issuer for unknown reason. | 5555 5568 7936 0005 | `card_declined` | `do_not_honor` | You receive `12/35` as expiry date | | The card does not have sufficient funds to complete the transaction. | 5555 5515 3049 0051 | `card_declined` | `insufficient_funds` | You receive `12/35` as expiry date | | The card was reported stolen. | 5555 5553 1674 0043 | `card_declined` | `stolen_card` | You receive `12/35` as expiry date | | The card used is not a test card. | xxxx xxxx xxxx xxxx | `invalid_test_card` | `N/A` | Not applicable | Regardless of the expiry details you send, `payment_method_details.card.expiry` always returns the fixed value `12/35` in every response and webhook payload while in test mode. ## Testing first-in-series and next-in-series payments ### Testing Cards When testing card payments, always use [test API keys](/api-reference/getting-started/authentication#operating-modes). Only use the specified test cards listed below. **Do not use real card** information under any circumstances. > Note: The 3DS challenge page will not be displayed during testing, as the flow is being simulated to mimic a frictionless authentication experience. In order to do an end-to-end first-in-series and next-in-series integration you can follow the below steps. Now you can use the same card number with different amount combinations to test different next-in-series scenarios. ### First-in-series Test: 1. Make a first-in-series API request using the PS API. 2. Render the session\_url on your client side and select cards. 3. Use the below card numbers and amount configurations to simulate the different scenarios. | Scenario | Outcome | Test Card Number | Amount
(in minor units) | Expiry Date | cvv | Notes | | :- | :- | :- | :- | :- | :- | :- | | The card needs to be authenticated and saved. | 0\$ Auth | 5555 5555 5555 7777 | 0 | Any date in the future | Any 3 digits | You receive `12/35` as expiry date | | The card needs to be authenticated, saved and an initial charge needs to be processed. | Success scenario | 5555 5555 5555 7777 | Any amount | Any date in the future | Any 3 digits | You receive `12/35` as expiry date | ### Next-in-series Test: 1. Make a next-in-series API request using the PS API 2. Use the below amount configurations in the API to simulate the different scenarios, while passing the customer\_id and payment\_method\_id in the API request. | Scenario | Outcome | Amount
(in minor units) | Error Code | Decline Code | Notes | | :- | :- | :- | :- | :- | :- | | The card was declined by the issuer for unknown reason. | Error scenario | 100000 | `card_declined` | `do_not_honor` | You receive `12/35` as expiry date | | The card does not have sufficient funds to complete the transaction. | Error scenario | 200000 | `card_declined` | `insufficient_funds` | You receive `12/35` as expiry date | | The card was reported stolen. | Error scenario | 300000 | `card_declined` | `stolen_card` | You receive `12/35` as expiry date | | Successful next-in-series card payment. | Success scenario | Any amount | `N/A` | `N/A` | Not applicable | ## Testing APM payment methods Success and failure scenarios for APM (Alternative Payment Methods) payments can be simulated by initiating a payment with a specific amount. The amount should be specified in [minor units](https://localhost:3001/api-reference/basics/monetary_amounts#minor-units-explained). ### Supported APM test scenarios βœ… **Success scenarios** | Test Amount | Webhook Fired | | :- | :- | | 100001 | Yes | ❌ **Error scenarios** | Test Amount | Webhook Fired | Error Code | | :- | :- | :- | | 100002 | Yes | `insufficient_funds` | | xxxxxx | No | `invalid_test_amount` | ## Webhook Responses 1. [Payment Session Update](/api-reference/collect/payment_sessions/webhook/updated) - When testing through the Payment Session API, an update will be triggered and delivered to your configured webhook endpoint via the `payment_session.updated` event. # URL Parameters Source: https://docs.momentco.io/api-reference/getting-started/url_params Learn how to track marketing campaign attributes using URL parameters on bill presentment ## Overview Moment enables you to drive payment volume directly via your marketing and customer lifecycle strategies β€” including EDM, digital advertising, messaging, and more β€” by enabling you to: * **Track marketing campaign attributes** as URL parameters through the **Electronic Bill Presentment (EBP)** journey (e.g. `utm_*`) * **β€œFast Forward” customers** through the payment journey by pre-filling their account identifier and pre-populating their product or purchase selections to skip pages, reducing clicks * **Directly collect payments** from new prospect customers and complete sign-up after they’ve successfully made an optional product selection and their payment * **Attribute sales partner resources**, including field agents and affiliate marketing programs * **Customize your reporting** endlessly by tracking both supported fields and arbitrary fields unique to your business * **Use optional URL parameter behaviors** to give you the customization you need ```text theme={"system"} https://{merchant}.momentpay.io/{country?}/{id?}? ``` ## URL parameters β€” details * `country` and `id` are optional path parameters (before `?`). * Remaining fields are standard query parameters placed after `?` to populating the `` * A wide range of Supported Parameters are listed below, in addition * Unknown Parameters not listed below will be captured and stored as Metadata. ### Encoding & limits * URL-encode values (including spaces as `%20` or `+`). * We recommend the list of URL parameters to be ≀ **256** chars, up to a total URL length of ≀ **\~2,000** chars * Parameter names and values are **case-insensitive**. *** ## Supported parameters | **Parameter** | **Required?** | **Description** | **Examples** | | - | - | - | - | | `country` (path) | Optional | ISO-3166-1 alpha-2 country code | `ZA`, `NG`, `KE` | | `id` (path) | Optional | **MSISDN** (digits only, no country code), **email**, or an **alphanumeric customer identifier** (e.g., CRM/user ID); appears **before `?`** | `0821234567`, `821234567`; `user@example.com` | | `lg` | Optional | 2-letter language code | `en`, `fr`, `pt` | | `affiliate` | Optional | Affiliate/channel identifier | `AFFILIATE_A`, `SOCIAL` | | `agent` | Optional | Sub-affiliate/agent ID | `agent-5521`, `a42f7` | | `product_code` | Optional | Product identifier for pre-selection (uppercase + `_` recommended) | `BASIC_MONTHLY`, `PREMIUM_ANNUAL` | | `allow_product_selection` | Optional | Boolean to force **show the product chooser** even if `product_code` is sent. | `true`, `false` | | `utm_source` | Optional | UTM: traffic source | `braze`, `facebook`, `newsletter` | | `utm_medium` | Optional | UTM: medium | `email`, `sms`, `cpc` | | `utm_campaign` | Optional | UTM: campaign name | `summer_sale_2025`, `winback_q4` | | `utm_term` | Optional | UTM: keyword | `sports+subscription` | | `utm_content` | Optional | UTM: creative/variant | `hero_banner_a`, `cta_blue` | | `metadata` | Optional | Free-form key/value pairs as a single field. Format: `key=value\|key2=value2` (values URL-encoded). | `lid=wgc78f63m4sr\|segment=A1` | ## Behaviour and precedence ### Defaults All parameters are optional. Missing or invalid values are ignored and EBP / Checkout will continue with default behaviour. ### Fast Forward By pre-filling valid parameters, customers can be skipped through the journey * Provide a valid `id` for the customer and they’ll skip searching for their account. * With a valid `product_code` **and** `allow_product_selection=false`, they’ll skip directly to the payment step with that product (e.g. a promotional item) pre-selected. * With a valid `product_code` **and** `allow_product_selection=true`, they’ll see the product pre-selected but will have the option to choose an alternative from the product chooser. ### Storage * Known parameters (`utm_*`, `affiliate`, `agent`, `product_code`, `metadata` etc.) are stored as they are. * Unknown parameters are also stored in `metadata` alongside the original value of the metadata field. It keeps arbitrary keys out of the top-level namespace while still allowing flexible context. ### Surfacing Captured values are available in **webhooks**, **exports**, and **analytics** (where they have been enabled for your Moment merchant account). *** ## Examples **Email `id`, UTM, product pre-select** ```text theme={"system"} https://example.momentpay.io/ZA/user%2Bpromo@example.com?utm_source=newsletter&utm_medium=email&utm_campaign=summer_sale_2025&product_code=PREMIUM_ANNUAL&allow_product_selection=false&affiliate=AFFILIATE_A&agent=agent-5521&metadata=lid%3Dwgc78f63m4sr%7Csegment%3DA1 ``` **MSISDN `id`, (no country code), force chooser** ```text theme={"system"} https://example.momentpay.io/NG/0821234567?utm_source=sms&utm_medium=sms&utm_campaign=winback_q4&allow_product_selection=true&metadata=ref%3Dabc123%7Clocale%3Den ``` ## `metadata` behaviour * If a **known parameter** is passed **inside `metadata`** (as `key=value`), it is **stored as a typed field** just like when sent as a top-level query param. The raw `metadata` string is also preserved. * If an **unknown parameter** is present in the URL, it is **added to the `metadata` blob** (in addition to any explicit `metadata` value you send). > **Format reminder**\ > `metadata` is a single string of URL-encoded pairs joined by `|`, e.g. `key=value|key2=value2`.\ > Unknown top-level params are recorded as `key=value` entries inside `metadata`. *** ### Examples #### 1. Only `metadata` provided (no unknown params) **URL:** ```text theme={"system"} https://example.momentpay.io/ZA/user%2Bpromo@example.com?metadata=value ``` **Stored** ```text theme={"system"} metadata: "value" ``` #### 2. `metadata` provided and an unknown top-level param **URL:** ```text theme={"system"} https://example.momentpay.io/ZA/user%2Bpromo@example.com?unknownparam-1=value-1&metadata=value ``` **Stored** ```text theme={"system"} metadata: "value|unknownparam-1=value-1" ``` #### 3. Only unknown top-level param (no metadata provided) **URL:** ```text theme={"system"} https://example.momentpay.io/ZA/user%2Bpromo@example.com?unknownparam-1=value-1 ``` **Stored** ```text theme={"system"} metadata: "unknownparam-1=value-1" ``` # Overview Source: https://docs.momentco.io/api-reference/webhooks/overview Learn how the API delivers event notifications Webhooks deliver notifications of events as HTTP requests whenever important data within your account changes. This allows you to react to events in real time and eliminates the need for polling. *** ## Getting Started
Register your endpoint, understand request headers, event structure, and retry behaviour. Verify event authenticity, prevent replay attacks, and handle duplicate events.
*** ## Events
Events for session creation, updates, and completion. Events for payment request creation, updates, and payment. Events for payments received on hosted payment pages. Events for payment success, failure, capture, and void. Events for subscription creation, ending, and cancellation. Events for redemption creation, confirmation, and failure. Events for billing lifecycle activity.
# Setup Source: https://docs.momentco.io/api-reference/webhooks/setup Register your endpoint and start receiving webhook events To start receiving webhook events, reach out to the [customer success](/documentation/support) team to: 1. Register the URL of an endpoint to receive webhooks 2. Receive a secret signing key that will be generated and shared with you, essential for ensuring the authenticity of incoming payloads and preventing malicious requests Ensure that the URL you specify points to a secure endpoint using HTTPS, and can receive POST requests. The secret signing key can be rotated on request. We suggest using a tunneling service such as [ngrok](https://ngrok.com) to test your webhook implementation. *** ## Request Headers When we send a webhook event, the HTTP request includes three essential headers: * `webhook-id`: An ID that uniquely identifies each message. * `webhook-timestamp`: A [Unix timestamp](https://en.wikipedia.org/wiki/Unix_time) indicating when the event was signed. This timestamp is included in the signature to guard against replay attacks. See [Verification](/api-reference/webhooks/verification) for more details. * `webhook-signature`: A list of signatures (HMAC-SHA256) created using a secret signing key, used to validate the authenticity of an event. Typically the signature list consists of only one element, but it can contain any number of signatures. It is formatted as a space-delimited list of signatures and their corresponding version identifiers. See [Verification](/api-reference/webhooks/verification) for more details. For example: ``` v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE= v1,bm9ldHUjKzFob2VudXRob2VodWUzMjRvdWVvdW9ldQo= ``` *** ## Processing Events The endpoint on your server must be capable of receiving HTTPS POST requests. Respond with a 2xx status code within 15 seconds to confirm receipt. If no 2xx status code is received, the system retries delivery according to this schedule: * Immediately * 5 seconds * 5 minutes * 30 minutes * 2 hours * 5 hours * 10 hours * Additional 10 hours After the conclusion of the above attempts the event will be marked as **Failed** for this endpoint. If all attempts to a specific endpoint fail for a period of 5 days, the endpoint will be disabled. *** ## Structure of an Event The event payload of a webhook request sent to the registered endpoint will contain: * `id`: An ID that uniquely identifies each event. * `type`: The type of event that triggered the webhook, for example `payment_session.created` or `payment_session.updated`. * `data`: The actual payload, which can be a different object depending on the event type. For example, for `payment_session.*` events, the payload will always be the `payment_session` object. # Verification Source: https://docs.momentco.io/api-reference/webhooks/verification Verify the authenticity of webhook events and prevent duplicate processing ## Verifying Events Webhooks can be vulnerable to security risks, such as malicious actors sending fake events or replaying intercepted requests. To safeguard against these threats, you should **prevent replay attacks**, **verify the origin and authenticity of events**, and **prevent duplicate processing of events**. A **replay attack** can be prevented by blocking intercepted requests from being reused. Every webhook includes the `webhook-timestamp` request header, which provides the [Unix timestamp](https://en.wikipedia.org/wiki/Unix_time) of when the event was signed. Use this timestamp to prevent [replay attacks](https://en.wikipedia.org/wiki/Replay_attack): * Ensure the `webhook-timestamp` is within a valid time window (we recommend up to 3 minutes). * Since retries generate a new timestamp for each attempt, you can confidently validate each request. * Reject requests with timestamps outside the threshold to block tampered or replayed events. **Verify the origin and authenticity of events** by using the **secret signing key** unique to your account, and the request header `webhook-signature` included as part of the webhook. We sign all webhooks originating from us using the **secret signing key**. Follow these steps to ensure the event was sent by us: * **Generate the signed content** by concatenating the values of the `webhook-id` request header, `webhook-timestamp` request header, and a string representation of the request body into a single string, separated by periods. * **Calculate the expected signature**, using the **secret key** and the **signed content** above to generate a `HMAC` `SHA256` signature. Then, encode this value using a `base64` encoding. * **Verify the signature** by comparing the **expected signature** with each signature in the `webhook-signature` header. The header may contain multiple space-delimited signatures β€” if the generated signature matches **any one** of them, you can proceed with processing the event. - Make sure to use the string representation of the request body i.e. the raw request body as any minor modification to the body will generate a completely different signature. - Before calculating the expected signature, ensure that you exclude the whsec\_ prefix from the secret signing key. - Before verifying the signature, make sure to remove the version prefix and delimiter (e.g. v1,). - When comparing the signatures, it is recommended to use a constant-time string comparison method to prevent timing attacks. Moment may periodically rotate your webhook signing secret for security purposes. During rotation, the `webhook-signature` header will contain multiple space-delimited signatures (e.g. `v1, v1,`). Your verification logic should accept a request if **any** of the provided signatures is valid, ensuring uninterrupted event processing during the transition period. The sample code below demonstrates this. *** ## Handling Duplicate Events Duplicate processing of events can be prevented at the request-level by processing events in an idempotent-safe manner. Each webhook request includes both a `webhook-id` in the request header and an `event-id` in the message body. The `webhook-id` uniquely identifies a single delivery attempt, while the `event-id` uniquely identifies the event itself across all retries or replays of the same event. To prevent duplicates, store the `webhook-id` in a persistent data store and ensure that a request with the same identifier has not been processed before. Using both identifiers ensures that duplicate HTTP deliveries and duplicate logical events are handled safely. Beyond request-level handling, integrations should always implement operation-level idempotency, as request-level deduplication is often only applied over a limited time window. Operation-level idempotency ensures that repeated or delayed business events do not cause inconsistent state changes after that window has expired. Before applying an update to a resource such as a `payment_session` or a `payment`, inspect the payload and the current state of the object: * If the `payment_session` (as identified by the payment session identifier) is already in a final state, for example, `completed`, `expired`, or `canceled`; or a `payment` (as identified by the payment identifier) is already in a final state, for example, `succeeded`, ignore the event by responding with a 2xx status code to prevent redelivery β€” since no further state change is needed. * Only apply updates that move the object forward, for example: * `payment_session`: `active` β†’ `completed` * `payment`: `draft` β†’ `succeeded` Combining request-level and operation-level idempotency ensures that duplicate or delayed webhook events are non-destructive, keeping states consistent even when events are retried or replayed beyond the initial request-handling window. *** ## Additional Security Precautions In addition to the security measures used to verify the origin and authenticity of webhook events described above, all webhook events are sent from a specific set of source static IP addresses. If your webhook endpoint is hosted behind a firewall or a NAT (Network Address Translation) gateway, you'll need to ensure it can receive incoming requests. Whitelist the following source static IP addresses in your network configuration so that webhook traffic is not blocked or dropped by your security settings. ``` 52.215.16.239 54.216.8.72 63.33.109.123 2a05:d028:17:8000::/56 ``` *** ## Sample Code ```javascript webhooks.js theme={"system"} // Using NodeJs + Express app.post("/my/webhook/url", function(req, res) { const requestBody = req.rawBody; // TODO: Verify and process the received data res.send(200); }); ``` ```python webhooks.py theme={"system"} # Using Python + Flask @app.route('/my/webhook/url', methods=['POST']) def webhook(): request_body = request.get_data(as_text=True) # TODO: Verify and process the received data return "", 200 ``` **Generate the signed content:** ```javascript generate.js theme={"system"} // Using NodeJs + Express const headers = req.headers; const requestBody = req.rawBody; const id = headers['webhook-id']; const timestamp = headers['webhook-timestamp']; const signedContent = `${id}.${timestamp}.${requestBody}`; ``` ```python generate.py theme={"system"} # Using Python + Flask headers = request.headers request_body = request.get_data(as_text=True) id = headers.get('webhook-id') timestamp = headers.get('webhook-timestamp') signed_content = str(id) + '.' + str(timestamp) + '.' + str(request_body) ``` **Calculate the expected signature:** ```javascript calculate.js theme={"system"} const crypto = require('crypto'); const secret = 'whsec_MDUyYmQ2NThjZDY3NDU5NzkyYTViNjViOTlhYzI2MzE='; const secretBytes = Buffer.from(secret.split("_")[1], "base64"); const expectedSignature = crypto .createHmac('sha256', secretBytes) .update(signedContent) .digest('base64'); ``` ```python calculate.py theme={"system"} secret = 'whsec_MDUyYmQ2NThjZDY3NDU5NzkyYTViNjViOTlhYzI2MzE=' secret_bytes = base64.b64decode(secret.split('_')[1]) hmac_signature = hmac.new(secret_bytes, signed_content.encode(), hashlib.sha256).digest() expected_signature = base64.b64encode(hmac_signature).decode() ``` **Verify the signature:** ```javascript verify.js theme={"system"} // The header is space-delimited; each entry is "v1,". // During key rotation multiple signatures are present β€” accept if any matches. const signatures = headers['webhook-signature'].split(' '); const isValid = signatures.some(entry => { const [version, sig] = entry.split(','); if (version !== 'v1' || !sig) return false; try { return crypto.timingSafeEqual(Buffer.from(expectedSignature), Buffer.from(sig)); } catch { return false; } }); if (isValid) { // process webhook event return res.sendStatus(200); } // do not process webhook event return res.sendStatus(403); ``` ```python verify.py theme={"system"} # The header is space-delimited; each entry is "v1,". # During key rotation multiple signatures are present β€” accept if any matches. signatures = headers.get('webhook-signature', '').split(' ') is_valid = any( hmac.compare_digest(parts[1], expected_signature) for entry in signatures for parts in [entry.split(',')] if len(parts) == 2 and parts[0] == 'v1' ) if is_valid: # process webhook event return "", 200 # do not process webhook event return "", 403 ``` ```javascript webhooks.js theme={"system"} const express = require('express'); const crypto = require('crypto'); const { getRawBody } = require('raw-body'); const app = express(); app.use((req, res, next) => { getRawBody(req, { length: req.headers['content-length'], encoding: 'utf-8' }, (err, rawBody) => { if (err) return next(err); req.rawBody = rawBody; next(); }); }); app.post("/my/webhook/url", function(req, res) { const headers = req.headers; const requestBody = req.rawBody; const id = headers['webhook-id']; const timestamp = headers['webhook-timestamp']; // Reject if timestamp is more than 10 minutes old or in the future const TOLERANCE_SECONDS = 10 * 60; const now = Math.floor(Date.now() / 1000); if (Math.abs(now - parseInt(timestamp, 10)) > TOLERANCE_SECONDS) { return res.sendStatus(403); } const signedContent = `${id}.${timestamp}.${requestBody}`; const secret = 'whsec_MDUyYmQ2NThjZDY3NDU5NzkyYTViNjViOTlhYzI2MzE='; const secretBytes = Buffer.from(secret.split('_')[1], "base64"); const expectedSignature = crypto .createHmac('sha256', secretBytes) .update(signedContent) .digest('base64'); // The header is space-delimited; each entry is "v1,". // During key rotation multiple signatures are present β€” accept if any matches. const signatures = headers['webhook-signature'].split(' '); const isValid = signatures.some(entry => { const [version, sig] = entry.split(','); if (version !== 'v1' || !sig) return false; try { return crypto.timingSafeEqual(Buffer.from(expectedSignature), Buffer.from(sig)); } catch { return false; } }); if (isValid) { return res.sendStatus(200); } return res.sendStatus(403); }); app.listen(3000, () => { console.log('Server is running on port 3000'); }); ``` ```python webhooks.py theme={"system"} from flask import Flask, request import hashlib import base64 import hmac import time app = Flask(__name__) @app.route('/my/webhook/url', methods=['POST']) def webhook(): headers = request.headers request_body = request.get_data(as_text=True) webhook_id = headers.get('webhook-id') timestamp = headers.get('webhook-timestamp') # Reject if timestamp is more than 10 minutes old or in the future TOLERANCE_SECONDS = 10 * 60 now = int(time.time()) if abs(now - int(timestamp)) > TOLERANCE_SECONDS: return "", 403 signed_content = str(webhook_id) + '.' + str(timestamp) + '.' + str(request_body) secret = 'whsec_MDUyYmQ2NThjZDY3NDU5NzkyYTViNjViOTlhYzI2MzE=' secret_bytes = base64.b64decode(secret.split('_')[1]) hmac_signature = hmac.new(secret_bytes, signed_content.encode(), hashlib.sha256).digest() expected_signature = base64.b64encode(hmac_signature).decode() # The header is space-delimited; each entry is "v1,". # During key rotation multiple signatures are present β€” accept if any matches. signatures = headers.get('webhook-signature', '').split(' ') is_valid = any( hmac.compare_digest(parts[1], expected_signature) for entry in signatures for parts in [entry.split(',')] if len(parts) == 2 and parts[0] == 'v1' ) if is_valid: return "", 200 return "", 403 if __name__ == '__main__': app.run() ``` # Accepting Payments Source: https://docs.momentco.io/documentation/billing/accepting-payment What happens when a payment is received against a bill Bills can be collected online via Electronic Bill Presentment or in person via Moment's off-platform network. For how each channel works, see [Presenting Bills](/documentation/billing/ebp). Regardless of channel, Moment handles payment processing and notifies the merchant in real time. *** ## After Payment Once a payment is received through either channel: * The bill is automatically marked as **paid**. * A receipt is provided to the customer. * Payment status is updated in real time via the **Client Portal** and **webhooks**. See [Webhooks](/documentation/billing/webhooks) for the events fired on payment. *** For digital-first merchants who want to extend collection offline without a full Billing integration, [Paycode](/documentation/network/paycode) enables customers to start a payment in Checkout and complete it at any in-person pay point using a unique code. # Core Concepts Source: https://docs.momentco.io/documentation/billing/core-concepts The Billing data model and the three billing models The Billing API is organised around three objects: **Customer**, **Account**, and **Bill**. These objects support three distinct billing models. A merchant typically operates with one billing model that reflects how their business tracks what customers owe. *** ## Billing Models ### Customer Balance A balance is held directly on the customer and updated as payments come in. This is the simplest model and suits merchants who track a single running amount owed per customer without needing to break it down further. The customer's `balance` field represents the current amount outstanding. A positive balance indicates an amount owed by the customer (a receivable). A negative balance indicates a credit held on behalf of the customer (a payable), most commonly in prepaid or wallet scenarios where the merchant holds value the customer has already funded. It also arises after an overpayment or credit adjustment. When a payment is received, Moment sends an `obligation.amount_applied` webhook and you reconcile the payment against the customer's balance. πŸ‘‰ [Quickstart: Customer Balance](/api-reference/billing/quickstart-customer-balance) ### Account Balance Balances are tracked across one or more accounts within a customer. Each account carries its own independent balance. This model suits merchants who need to separate what a customer owes across different products, services, or cost centres. The customer record acts as a grouping entity. Balances live on the accounts below it. As with the Customer Balance model, an account's `balance` can be positive (amount owed) or negative (credit on the account). When a payment is received against an account, Moment sends an `obligation.amount_applied` webhook with the `account_id` of the account that was paid. πŸ‘‰ [Quickstart: Account Balance](/api-reference/billing/quickstart-account-balance) ### Bill Individual bills are raised against a customer or account with a specific `amount_due` and optional `due_date`. Each bill has its own lifecycle and payment status, tracked independently. This model suits merchants who issue discrete invoices. When a payment is received against a bill, Moment sends an `obligation.amount_applied` webhook with the `bill_id` of the bill that was paid. πŸ‘‰ [Quickstart: Bill Amount Due](/api-reference/billing/quickstart-bill-amount-due) *** ## The Data Model ### Customer Customers are the top-level entity. Every billing relationship starts with a customer. * A customer can carry a `balance` (used in the Customer Balance model). Positive values represent amounts owed; negative values represent credits. * A customer can have multiple Accounts beneath it. * A customer can have Bills raised directly against it. ### Account Accounts are sub-entities within a customer used to track balances or bills at a finer level of granularity. * An account belongs to exactly one customer. * An account can carry a `balance` (used in the Account Balance model). Positive values represent amounts owed; negative values represent credits. * An account can have Bills raised against it. ### Bill A bill is a discrete payment obligation with a specific amount due. * A bill must belong to either a customer or an account. * A bill requires an `amount_due` and can optionally have a `bill_date` and `due_date`. * A bill has a `status` of `unpaid` or `paid`. ### Relationships * A **Customer** can have many **Accounts**. * A **Customer** can have many **Bills** raised directly. * An **Account** can have many **Bills**. # Presenting Bills Source: https://docs.momentco.io/documentation/billing/ebp Online and in-person bill presentment channels Bills created in the Billing platform can be presented and collected through two channels: online via Electronic Bill Presentment, and in person across Moment's off-platform network. Both are available once a bill is created, with no additional integration required. *** ## Online: Electronic Bill Presentment For merchants seeking a no-code solution to accept online payments, Electronic Bill Presentment (EBP) provides a ready-to-use hosted portal. Customers receive a link, open it to view their outstanding bill, and pay using any of Moment's supported online payment methods. ### Key Features * **No-Code Integration**\ Launch without writing a single line of code. Merchants receive a pre-configured bill presentment page with instant access to all supported online payment methods. * **Customisable Payment Page**\ Tailor the bill presentment page with a logo, colors, messaging, and branded subdomain to ensure a consistent brand experience. * **Real-Time Visibility**\ Customers can instantly view outstanding bills and make payments through the method of their choice, streamlining collections and reducing friction. ### Use Cases * **Omni-Channel Payment Reminders**\ Deliver bill reminders through email, SMS, or WhatsApp, allowing customers to pay through a single, unified payment page regardless of the communication channel. * **Online Payments Without Development**\ Enable online payments for any customer without additional development effort, ideal for businesses where full API integration is not necessary. ### URL Parameters Optional URL parameters can carry marketing and operational context into bill presentment (for example, `utm_*`, `affiliate`, `agent`, `product_code`). Values are captured and surfaced for attribution and reporting. * **Channel-Agnostic.** Works with email, SMS, WhatsApp, push, and affiliate links. * **Safe by Default.** Missing or invalid parameters are ignored; checkout continues with sensible defaults. * **Privacy-Aware.** Parameters are stored as metadata. * **Analytics-Ready.** Captured fields appear in webhooks, exports, and analytics. See [URL parameters for EBP payment links](/api-reference/getting-started/url_params). ### Getting Started To enable Electronic Bill Presentment: 1. Contact your Moment representative to provision the bill presentment page. 2. Provide brand assets (logo, color scheme) and preferred subdomain. 3. Start sending digital bills via the preferred communication channel. *** ## In-Person: Off-Platform Network Once a bill is created in the Billing platform, it is automatically visible on Moment's in-person network. Customers can walk into any pay point and pay in person. No additional setup or integration is required. Moment's in-person network spans more than 300,000 pay points across South Africa, including large retailers and local agents. When a customer pays in person: 1. The customer visits any supported pay point and requests to pay a bill. 2. The clerk retrieves the bill using the customer's identifier. Depending on the merchant's configuration, this can be a customer identifier, account identifier, or bill identifier. 3. The customer confirms the amount and completes payment. 4. Moment sends a real-time notification to the merchant. See [In-Person Network](/documentation/network/in-person-network) for the full list of supported pay points and the payer journey. # Integration Guide Source: https://docs.momentco.io/documentation/billing/file-integration-guide This guide explains how to implement the Billing File (SFTP File Ingestion) pattern with Moment.\ It enables merchants to present bills across Moment's online and in-person network using a low-code, file-based workflow. *** ## Pattern Overview A low-code, standardized pattern that works with any billing system using one pre-defined schema. Merchants: * Populate billing data * Deliver files via SFTP Moment handles validation, ingestion, and bill presentation. *** ## When to Use This Pattern Use this pattern if: * You want minimal engineering effort * You prefer SFTP file delivery over APIs * You want the fastest path to bill presentment You may migrate to the Billing API later for real-time needs. *** ## Integration Responsibilities ### What the Merchant Provides #### Billing File Format Each billing file includes: 1. **Customer information** (Items 1–6) 2. **Amount payable** (Items 7–8) 3. **Optional metadata** β€” biller-specific fields that support internal system requirements *** #### File Schema | Item | Item Name | Field Name | Type | Example | Required | | - | - | - | - | - | - | | 1 | First Name | first\_name | String | John | True | | 2 | Last Name | last\_name | String | Doe | True | | 3 | Passport Number | passport\_number | String | P12345678 | Either 3 or 4 | | 4 | SA ID Number | sa\_identification\_number | Decimal | 8501015800085 | Either 3 or 4 | | 5 | Mobile | mobile\_phone\_number | Decimal | 0821234567 | False | | 6 | Email | email | String | [john@example.com](mailto:john@example.com) | False | | 7 | Due Date | due\_date | Date | 2025-02-20 | False | | 8 | Amount Due | amount\_due | Decimal | 240.00 | True | **Identifier rules:** * Merchant may choose any unique identifier (account number, policy number, subscriber ID, etc.) * Prefer numeric, under 16 digits * Moment can generate identifiers on request *** #### File Delivery Requirements | File Type | Cadence | Delivery Time (UTC) | Type | Naming Convention | | - | - | - | - | - | | Full customer file | One-time | N/A | CSV | `[country]_[merchant]_moment_all-customers_YYYY-MM-DD.csv` | | Daily incremental file | Daily | 6am SAST | CSV | `[country]_[merchant]_dailyfile_YYYY-MM-DD.csv` | #### Missed File Handling If a daily file is missed, include all applicable updates in the next file. | Date | Daily File | Notes | | - | - | - | | 1 Feb | Not delivered | | | 2 Feb | Delivered | Includes updates from 1 and 2 Feb | *** ### What Moment Does * Validates file structure and content * Ingests bills * Makes bills discoverable via all payment channels * Generates settlement and reconciliation files *** ### What Customers See 1. Enter identifier 2. View bill details 3. Confirm and pay *** # End-to-End Flow **1. Billing File Creation**\ Merchant prepares a billing CSV file. **2. Secure File Delivery (SFTP)**\ Daily cadence may introduce up to 24 hours of data staleness. **3. Validation and Intake**\ Moment validates and ingests the file. **4. Enquiry at Payment Channel**\ Customer enters identifier β†’ Channel queries Moment β†’ Bill returned. **5. Payment**\ Customer pays β†’ Channel notifies Moment. **6. Reconciliation**\ Moment provides webhook notifications or SFTP settlement files. *** ## Sequence Flow ```mermaid theme={"system"} sequenceDiagram participant Biller as Biller System participant Moment as Moment Platform participant Channel as Payment Channel participant Customer as Customer %% Billing data (file-based) Biller->>Moment: Upload billing file (SFTP) Moment-->>Biller: Acknowledge file (optional) %% Enquiry Customer->>Channel: Enter identifier Channel->>Moment: Bill enquiry (identifier) Moment-->>Channel: Bill details (amount, metadata) Channel-->>Customer: Display bill details %% Payment Customer->>Channel: Confirm payment Channel->>Moment: Payment request Moment-->>Channel: Payment result Channel-->>Customer: Show receipt / confirmation %% Reconciliation Moment->>Biller: Reconciliation & settlement files (SFTP) Biller-->>Biller: Update internal ledger / post payments ``` *** # Best Practices and Notes ## Identifier Consistency The identifier used in your billing file must be the same identifier entered by customers during enquiry. The merchant defines this identifier (e.g., account number, subscriber ID, policy number). *** ## Data Freshness Choose a file delivery cadence that aligns with your business needs: | Cadence | Trade-Off | | - | - | | Hourly | Near real-time updates; higher operational overhead | | Daily | Most common; straightforward to maintain; amounts may be up to 24 hours old | | Weekly | Suitable for non-urgent billing cycles | *** ## Fulfillment and Financial Reporting * SFTP reconciliation and settlement files are the source of truth * Optional webhooks allow near-real-time notifications * Reconciliation files should be used for internal posting and ledger updates *** ## Security * SFTP keys and access credentials are provisioned securely during onboarding * Access is permissioned per merchant and monitored # Overview Source: https://docs.momentco.io/documentation/billing/overview Introduction to Bill Payments The Billing platform enables merchants to manage customers, accounts, and bills, and to collect payments across two channels: online via Electronic Bill Presentment, and in-person across Moment's off-platform network of more than 300,000 pay points. Once a bill is created, both channels are available with no additional integration.
Understand the three billing models and the Customer, Account, and Bill data model. Common billing scenarios and which model to use for each. Bulk-load bills into Moment via file or API. Present bills to customers online via Electronic Bill Presentment or in person across Moment's off-platform network. How payments are collected online and in person, and what happens after a payment is received. Track payment events in real time via webhook notifications. Integrate with Moment using file-based data exchange.
# Uploading Bills Source: https://docs.momentco.io/documentation/billing/uploading-bills Ingesting Billing data on Moment We support three primary methods for ingesting customersβ€”individually or along with their associated accounts, bills, and identifiers. Each method is designed to provide flexibility and scalability to meet your business needs. ## **SFTP File Ingestion** This method is ideal for bulk or scheduled ingestion (daily or bi-daily). It allows billers to upload structured files containing customer and billing data to a designated FTP location. Moment automatically processes these files to create or update customer records. **Key Features:** * Supports CSV (as per the agreed schema). * Requires biller-specific configuration for field mapping and validation. * Enables automated processing and reliable ingestion for large datasets. ## **Billing APIs** For real-time or programmatic ingestion, we offer a suite of RESTful APIs. These APIs are ideal for integrating with your internal systems to add or update customers, their accounts, and associated bills. **Capabilities:** * Create and update customer records on demand. * Manage associated accounts and bills dynamically. * Enable seamless integration with external CRM or billing platforms. πŸ‘‰ [See API Reference](/api-reference/billing/overview) ## **Dashboard (No-Code)** `COMING SOON` A web-based portal for manually uploading and managing customers and bills will soon be availableβ€”ideal for partners who prefer UI-based workflows over automation. *** ## **Next Steps** πŸ‘‰ [Read the API Reference](/api-reference/billing/overview)\ πŸ‘‰ [See Presenting Bills](/documentation/billing/ebp) *** # Use Cases Source: https://docs.momentco.io/documentation/billing/use-cases Common billing scenarios and which model to use Use this page to identify which billing model fits your integration. Each scenario maps to one of the three supported models. See [Core Concepts](/documentation/billing/core-concepts) for a full explanation of how the models differ. *** ## Rolling Statement Balance **Scenario:** A customer has a monthly statement balance that reflects their current outstanding amount. The balance is updated at each billing cycle to capture new charges, adjustments, or credits. A positive balance represents an amount owed by the customer; a negative balance represents a credit on the account. **Model:** Customer Balance. Set or update the `balance` on the customer each cycle. **Examples:** Prepaid and postpaid utilities, mobile airtime top-up, loyalty wallet balance. πŸ‘‰ [Quickstart: Customer Balance](/api-reference/billing/quickstart-customer-balance) *** ## Multi-Service or Multi-Product Balance **Scenario:** A customer is billed for multiple services that need to be tracked and settled independently. Each service has its own outstanding balance, and a customer may pay one without paying all. Balances can be positive (amount owed) or negative (credit on that account). **Model:** Account Balance. Create one account per service or product under the customer. Each account carries its own balance. **Examples:** Telecoms providers billing data, voice, and device repayments separately. Insurance companies tracking multiple policy premiums under one policyholder. πŸ‘‰ [Quickstart: Account Balance](/api-reference/billing/quickstart-account-balance) *** ## Prepaid or Wallet Balance **Scenario:** The merchant holds an active balance on behalf of the customer, typically funded through top-ups. The balance decreases as the customer consumes the service and increases when they add funds. Because the merchant owes the customer the remaining value, the balance is represented as a negative number (a credit). **Model:** Customer Balance or Account Balance. Set the `balance` to a negative value to represent the credit held. Update it as the customer tops up or consumes value. **Examples:** Gaming credits, prepaid airtime and data, prepaid utility balances, stored-value loyalty wallets. πŸ‘‰ [Quickstart: Customer Balance](/api-reference/billing/quickstart-customer-balance) *** ## Discrete Invoice Billing **Scenario:** A specific invoice is raised for a customer with a fixed amount due and a payment deadline. Each invoice is tracked independently with its own payment status. **Model:** Bill. Raise a bill with `amount_due` and `due_date` against the customer. **Examples:** Municipality rates and taxes, professional services invoices, ad-hoc charge collection. πŸ‘‰ [Quickstart: Bill Amount Due](/api-reference/billing/quickstart-bill-amount-due) *** ## Multi-Service Invoice Billing **Scenario:** A customer receives separate invoices per service each cycle. Each invoice has its own amount and due date and needs to be tracked independently, but services still need to roll up to a single customer for reporting. **Model:** Bill, with accounts used to group services. Raise bills against each account rather than directly on the customer. **Examples:** Municipal billing for water, electricity, and refuse as separate line items. Government agencies issuing bills per department or service type. πŸ‘‰ [Quickstart: Bill Amount Due](/api-reference/billing/quickstart-bill-amount-due) # Webhooks Source: https://docs.momentco.io/documentation/billing/webhooks Tracking billing payment events via webhooks Moment sends webhook events to your registered endpoint when payments are received against billing obligations. Use these to reconcile balances, update bill statuses, trigger downstream workflows, or notify customers. ## Available Events * **`obligation.amount_applied`** β€” Triggered when an amount is applied against a customer balance, account balance, or bill. The event payload identifies the obligation type via `customer_id`, `account_id`, or `bill_id` depending on what was paid. See [obligation.amount\_applied](/api-reference/billing/webhook/obligation_amount_applied). Additional events such as `bill.created`, `bill.updated`, and `customer.created` are coming soon. ## Setup See [Webhook Setup](/api-reference/webhooks/setup) to register your endpoint and [Webhook Verification](/api-reference/webhooks/verification) to validate incoming events. ## Handling Payments For step-by-step examples of handling the `obligation.amount_applied` event for each billing model, see the quickstarts: * [Quickstart: Customer Balance](/api-reference/billing/quickstart-customer-balance) * [Quickstart: Account Balance](/api-reference/billing/quickstart-account-balance) * [Quickstart: Bill Amount Due](/api-reference/billing/quickstart-bill-amount-due) # Checkout Source: https://docs.momentco.io/documentation/collect/checkout The hosted payment experience that powers all payment flows Checkout is a fully hosted payment experience. Customers see the merchant's brand, select from the payment methods available to them, and complete their transaction in a clean, guided flow. There is no payment interface to design or build.
Checkout payment method selection
*** ## Available across all payment products * [Payment Sessions](/documentation/collect/payment-sessions/overview), for programmatic, developer-driven payment flows * [Payment Pages](/documentation/collect/payment-links/payment-pages), a hosted page customers can pay from on demand * [Payment Requests](/documentation/collect/payment-links/payment-requests), a payment link sent directly to a customer * [Electronic Bill Presentment (EBP)](/documentation/billing/ebp), a hosted portal where customers view and pay outstanding bills *** ## Benefits * **Checkout that works everywhere.** Optimised for desktop and mobile, and delivers the same experience regardless of which product or channel initiated the payment. * **Checkout that converts.** Merchant branding is applied automatically. Returning customers can reuse saved payment methods for faster checkout. Optimised routing directs each attempt for the best chance of success, and if an attempt fails, the customer can try a different method without starting over. * **Checkout that compounds.** Every payment method added to the network becomes available in Checkout automatically, with no integration changes required. *** ## Payment methods Checkout displays only the payment methods enabled for the merchant account and supported in the customer's region, based on the country of operation and configured currencies. Available categories include Cards, Banks, EFTs, Wallets, Pay-in-person, and instructions-only methods such as vouchers and USSD. For a full list of available payment methods, see [Payment Methods](/documentation/network/payment-methods). *** ## Security Checkout is PCI-DSS compliant and supports 3D Secure for eligible card transactions. *** For a full walkthrough of each payment method flow and payment status screens, see the [Checkout Guide](/guides/payment-flows/checkout). # Overview Source: https://docs.momentco.io/documentation/collect/overview Introduction to Collect **Collect** is a core product vertical at Moment, purpose-built to empower businesses with seamless, flexible, and reliable payment collection tools. Whether through Payment Requests, invoices, subscriptions, or custom payment flows, Collect is designed to adapt to the diverse needs of modern merchants. Our mission is clear: to make collecting payments **effortless, secure, and scalable** across any channel, currency, or customer location.
A unified API for one-time payments, recurring collections, and authorisation and capture flows. Send customers a link to pay via Payment Requests or hosted Payment Pages.
## Key Capabilities * **Fast and Secure Collection** Enable quick, trusted payment experiences. * **Global Reach** Support multiple currencies and payment methods. * **Flexible Payment Flows** Support one-time payments, recurring collections, mandates, subscriptions, authorisation and capture, and payment method verification. * **Seamless Integration** Easily embed into existing systems and workflows. * **Real-Time Visibility** Get actionable insights and full control over payment activity. *** From one-time requests to complex recurring billing systems, **Collect powers it all in a single, unified platform**. # Overview Source: https://docs.momentco.io/documentation/collect/payment-links/overview Introduction to Payment Links **Payment Links** provide a fast, low-friction way to collect payments by sharing a link with your customers. No checkout flow to build and no app required. Customers open the link, complete payment, and you receive a real-time notification. Moment offers two types of Payment Links depending on your use case.
Generate a one-time payment link or trigger a bank push notification directly to the customer's banking app. Create a branded, hosted page that accepts multiple payments, supports custom fields, and can be shared anywhere.
# Payment Pages Source: https://docs.momentco.io/documentation/collect/payment-links/payment-pages Collect payments with customizable, branded payment pagesβ€”no code or app required ## **Overview** **Payment Pages** are purpose-built for branded, form-driven payment collection. Think of them as your online checkout page β€” fully customizable, ideal when you need more than just a payment. These pages are ideal for businesses, educators, NGOs, freelancers, and e-commerce sellers looking for a fast, no-code way to accept digital payments. *** ## **Key Features** * **Branded Pages:** Add your logo, stylesheet & custom messaging. * **Custom Fields:** Collect details like name, email, phone, quantity, or preferences * **Flexible Amounts:** Set fixed or allow user-entered amounts * **Media Support:** Add images and banners to enhance visual appeal *(Coming Soon)* * **Multi-Channel Sharing:** Distribute links via WhatsApp, SMS, email, or social media * **Catalog Integration:** Select and attach products from your catalog directly to hosted pages. Capture delivery/shipping details (address, city, state, country, pin) for fulfillment * **Maintain Products on Moment:** Store and manage your product catalog directly on the Moment platform for seamless checkout experiences * **Secure Payments:** PCI DSS-compliant with OTP and 3D Secure support * **Real-Time Updates:** Monitor from the dashboard or via webhooks *** ## **Common Use Cases** | Use Case | Description | | - | - | | **Events & Ticketing** | Sell tickets for workshops, concerts, or conferences and capture attendee details | | **Education & Training** | Collect tuition, course fees, or exam payments along with student information | | **Freelancers & Services** | Accept payments for services like consulting, coaching, writing, or designβ€”along with custom inputs | | **E-commerce & Catalog Sales** | Showcase product catalogs or bundles with quantity selection and checkout *(Coming Soon)* | | **Donations & Fundraising** | Create donation pages with fixed or flexible amounts, donor names, and messages | | **Healthcare & Clinics** | Accept appointment or service payments while capturing patient details and visit type | | **Workshops & Webinars** | Register and collect payments for online or offline sessions, with participant info | | **Real Estate & Bookings** | Collect booking or holding fees with customer and property preference details | | **Subscription Services** | Collect recurring or one-time subscription payments with subscriber preferences *(Coming Soon)* | | **Gyms & Fitness Studios** | Accept class bookings or membership fees along with health or schedule preferences | | **Religious & Community Groups** | Collect event donations, membership fees, or registration info for group activities | | **Nonprofits & NGOs** | Run seasonal campaigns, accept pledges, and collect donor data | | **Small Clinics & Therapists** | Collect advance payments for sessions and basic intake details | | **Crafts & Custom Products** | Gather order specifications (color, size, message) and collect payment for handmade items | *** ## **How It Works** 1. **Create a payment page** from the dashboard or via the `POST /collect/payment_pages` API 2. **Optionally Attach products from your catalog** or define custom fields 3. **Optionally capture delivery details** for order fulfillment 4. **Share the link** via WhatsApp, SMS, email, or social media 5. The customer **opens the link** and lands on a secure, branded payment page 6. The customer **completes the payment** using their preferred method 7. You can **track status** from the dashboard or through webhook notifications *** ## **Ways to Create Payment Pages** Moment supports both no-code and API-based approaches: ### **1. Dashboard (No-Code)** **Ideal for:** Teams who prefer manual setup through UI **Steps:** 1. Log into the dashboard 2. Go to **Hosted Pages** 3. Click **Create New Page** 4. Add page title, amount, description, optional expiry 5. Attach products from your catalog and toggle delivery collection if needed 6. Customize layout, fields, and branding 7. Publish and share the generated link *** ### **2. API Integration** **Ideal for:** Developers looking to automate page creation **Endpoints:** * `POST /collect/payment_pages` β€” Create a new payment page * `GET /collect/payment_pages/:id` β€” Fetch payment page details * `GET /collect/payment_pages/:id/transactions` β€” Fetch transactions related to Payment Pages * Webhook events β€” Get notified when someone pays ## **Status** ```mermaid theme={"system"} stateDiagram-v2 [*] --> active : Page published active --> expired : Expiry time reached (if expiry is set) active --> inactive : Manually disabled ``` | Status | Description | | - | - | | `active` | Page is live and accepting payments | | `inactive` | Page has been manually disabled | | `expired` | Page has automatically expired if an expiry time was configured | *** ## **Customization Options** | Feature | Description | | - | - | | **Logo & Branding** | Add your business identity for trust and consistency | | **Page Title & Description** | Customize messaging and visual appeal | | **Amount Input** | Use fixed values, ranges, or editable amounts | | **Custom Fields** | Collect form data like name, contact info, and user selections | | **Catalog Products** | Select items from your Moment catalog | | **Page Expiry** | Set automatic deactivation after a set time | *** ## **Getting Started** 1. **Sign up** as a merchant on Moment 2. Navigate to **Hosted Pages** from the dashboard 3. Add branding, amount, custom fields, and optional catalog products *(Coming Soon)* 4. Publish your page and share the link with your customers 5. View payments on your dashboard or subscribe to webhook notifications *** ## **Webhooks** Moment sends a webhook event to your registered endpoint each time a payment is received on a payment page. * **`payment_page.payment_received`** β€” Triggered when a payment is completed on a payment page. See [payment\_page.payment\_received](/api-reference/collect/payment_pages/webhook/payment_received). See [Webhook Setup](/api-reference/webhooks/setup) to register your endpoint and [Webhook Verification](/api-reference/webhooks/verification) to validate incoming events. *** ## **Next Steps** πŸ‘‰ [See Payment Pages API Reference](/api-reference/collect/payment_pages/overview)
πŸ‘‰ [See Webhook Events](/api-reference/collect/payment_pages/webhook/payment_received) # Payment Requests Source: https://docs.momentco.io/documentation/collect/payment-links/payment-requests Collect payments via shareable links or push notification. ## **Overview** **Payment Requests** let you collect money fast in two ways: * **Links:** Generate a pay link and share it anywhere. * **Push (Bank Push):** Trigger a payment request straight to the customer’s banking app (currently **Capitec Bank** only). No heavy integration, no complex checkoutβ€”just a simple path to getting paid. *** ## **Types of Payment Requests** | Type | How it works | Best for | | - | - | - | | **Link** | Create a URL that opens a secure checkout page. | One-time payments, DMs/WhatsApp, invoices. | | **Push (Bank Push)** | We send a payment request to the customer’s bank app; they approve it there. | High-trust flows, phone orders, COD, collections. | Push (Bank Push) is in Beta and currently supports Capitec Bank only. More banks coming soon. *** ## **Key Features** * **No Partial Payments:** Simplifies reconciliation by allowing only full payments. * **Multi-Channel Sharing (Links):** Send via SMS, WhatsApp, email, or social. * **Real-Time Tracking:** Monitor status in the dashboard or via webhooks. * **Secure Checkout:** PCI DSS, OTPs, 3D Secure, and fraud controls. * **Multiple Payment Methods (Links):** Cards, mobile money, bank transfers, and more. * **Fast Onboarding:** Get started in minutes. * **No-Code Friendly (Links):** Create and manage links from the dashboard (coming soon). * **Boost Cash Flow:** Reminders, expiries, and smart retries. * **High Conversion:** Optimized UX on a reliable, fast platform. * **Effortlessly Scalable:** Works for 10 or 10,000+ requests. * **Low Friction (Push):** No card entry; customer approves in their bank app. * **Lower Fraud Risk (Push):** Bank-authenticated approval flow. *** ## **Common Use Cases** | Use Case | Description | | - | - | | **Freelancers** | Request payments for servicesβ€”no code or setup. | | **Retail Stores** | Share links for phone or WhatsApp orders without a website or PoS. | | **Home Businesses** | Accept payments for custom orders (cakes, clothing, crafts). | | **Education & Clubs** | Collect fees, tuition, or event registrations. | | **Medical Practitioners** | Take appointment prepayments via SMS or email. | | **Delivery Agents** | Payment on delivery without cash handling. | | **Sales Teams** | Send instant payment requests post-deal closure. | | **Event Organizers** | Charge for RSVPs, tickets, or booth bookings. | | **Real Estate Agents** | Collect token/booking amounts for viewings or reservations. | | **Subscription Sellers** | Charge one-off or recurring dues without a full billing system. | | **Call Centers / Collections (Push)** | Trigger bank-approved payments during a live call. | Need help deciding between Links and Push? Use Links for broad reach and instant sharing. Use Push when your customer is on a call/chat and can approve directly in their banking app (Capitec). *** ## **How It Works** ### **A. Links** 1. Create a payment request (dashboard or API). 2. Share the unique URL via WhatsApp, SMS, email, or social. 3. Customer opens the secure checkout. 4. Customer pays with their preferred method. 5. You receive real-time status updates. ### **B. Push (Bank Push – Capitec)** 1. Create a push payment request via API (include customer identifier). 2. The customer receives a payment prompt in their **Capitec** app. 3. The customer reviews and **approves** the request. 4. Status updates flow to your system via API/webhooks. For Push, the customer must have an active and reachable Capitec banking app. The authorization window may be time-limited. *** ## **Ways to Create Payment Requests** Moment supports both no-code and developer options. ### **1) Dashboard (No-Code)** **Ideal for:** Teams wanting a manual, no-code flow. Our Client Portal Dashboard supports both single Payment Request creation and Bulk Payment Request creation (file upload with option to download batch of payment request links). Steps: 1. Log in to the dashboard 2. Open **Payment Requests** 3. Click **Create Payment Request** 4. Decide if you want to send it immediately via SMS, or just generate the link and send it later to your customer directly. 5. Enter amount, currency, description, expiry (optional) 6. Create the payment request 7. If send later, Share it with your customer See our [Payment Request guide](https://docs.momentco.io/guides/portal/payment-requests) for more detailed information and step-by-step directions. > Push (Bank Push) creation from dashboard is planned for a future release. *** ### **2) API Integration** **Ideal for:** Automating flows from CRMs, ERPs, or order systems. #### **Links** * **Create:** `POST /collect/payment_requests` with type `link`, this returns a unique `link_url` which can be forwarded for payment. * **Retrieve:** `GET /collect/payment_requests/:id` * **Webhook events:** Created, Paid & Updated. #### **Push (Capitec)** * **Create:** `POST /collect/payment_requests` with type `push`, this should send a push notification to the banking app. * **Retrieve:** `GET /collect/payment_requests/:id` * **Webhook events:** Created, Paid & Updated. *** ## **Status Lifecycles** ```mermaid theme={"system"} stateDiagram-v2 [*] --> active : Created active --> expired : Expired after time limit active --> paid : Payment Succeeded active --> failed : Payment failed ``` | Status | Links | Push | | - | - | - | | **active** | Link has been created and is valid. | Payment request created and notification sent to banking app. | | **paid** | Payment completed successfully. | Payment completed successfully. | | **expired** | Link expired (default: 24h). | Authorization or validity window elapsed without approval. | | **failed** | N/A | Payment failed or was declined by the customer. | ## **Customization Options** **Links** support: * **Logo/Branding** * **Payment description** * **Link expiry** * **External Reference** (tags or internal references) * **Metadata** **Push** uses the bank’s UI for approval. You can still pass: * **Description / Reference** (shown to the customer in-app where supported) * **External Reference & Metadata** (for your internal reconciliation) *** ## **Getting Started** 1. **Sign up** as a Moment merchant 2. Choose **Links** or **Push** for your use case 3. Create the request 4. Share the **link** or let the **bank app** prompt the customer (Push) 5. Track status via **API** and **Webhooks** *** ## **Webhooks** Moment sends webhook events to your registered endpoint as a payment request moves through its lifecycle. Use these to trigger downstream workflows, send confirmations, or update internal records. * **`payment_request.created`** β€” Triggered when a payment request is created. See [payment\_request.created](/api-reference/collect/payment_requests/webhook/created). * **`payment_request.updated`** β€” Triggered when a payment request status changes. See [payment\_request.updated](/api-reference/collect/payment_requests/webhook/updated). * **`payment_request.paid`** β€” Triggered when a payment request is fully paid. See [payment\_request.paid](/api-reference/collect/payment_requests/webhook/paid). See [Webhook Setup](/api-reference/webhooks/setup) to register your endpoint and [Webhook Verification](/api-reference/webhooks/verification) to validate incoming events. *** ## **Next Steps** * πŸ‘‰ [Payment Requests API Reference](/api-reference/collect/payment_requests/overview)
* πŸ‘‰ [Webhook Events](/api-reference/collect/payment_requests/webhook/created) # Captures and Voids Source: https://docs.momentco.io/documentation/collect/payment-sessions/authorisation-capture/captures-and-voids How to capture or void authorised payments ## Capture Scenarios ### Full capture Capture the entire authorised amount. Omit the `amount` field: ```json theme={"system"} POST /collect/payments/pay_rOaZNqKZMFxsRb2NBL1cj/captures Idempotency-Key: capture_order_123 // No request body needed ``` Response: ```json theme={"system"} { "id": "pay_rOaZNqKZMFxsRb2NBL1cj", "status": "succeeded", "amount": 100000, "capture_method": "manual", "authorised_amount": 100000, "paid_amount": 100000, "voided_amount": 0, ... } ``` **Webhook fired:** `payment.captured` ### Partial capture Capture less than the full authorised amount by specifying `amount`: ```json theme={"system"} POST /collect/payments/pay_rOaZNqKZMFxsRb2NBL1cj/captures Idempotency-Key: capture_order_123_shipment_1 { "amount": 60000 } ``` Response: ```json theme={"system"} { "authorised_amount": 100000, "paid_amount": 60000, "voided_amount": 0, ... } ``` The remaining R400.00 of authorisation can be voided: ```json theme={"system"} POST /collect/payments/pay_rOaZNqKZMFxsRb2NBL1cj/voids Idempotency-Key: void_order_123_remainder // No request body needed ``` ## Void **Best practice: void unused authorisations promptly.** A live authorisation reserves funds on the customer's card and reduces their available balance until it expires (typically 5 to 30 days depending on issuer and scheme). If you have decided not to capture, void the authorisation as soon as possible rather than letting it expire. This improves the customer experience and reduces support contact about held funds. ### Full void Release the entire remaining authorised amount: ```json theme={"system"} POST /collect/payments/pay_rOaZNqKZMFxsRb2NBL1cj/voids Idempotency-Key: void_order_123 // No request body needed ``` Response: ```json theme={"system"} { "authorised_amount": 100000, "paid_amount": 0, "voided_amount": 100000, ... } ``` **Webhook fired:** `payment.voided` ### Void after partial capture After a partial capture, void the remaining authorisation to close the payment: ``` 1. Authorisation: authorised_amount = 100000, paid_amount = 0, voided_amount = 0 2. Partial capture 60000: authorised_amount = 100000, paid_amount = 60000, voided_amount = 0 3. Void remaining: authorised_amount = 100000, paid_amount = 60000, voided_amount = 40000 ``` # Error Handling Source: https://docs.momentco.io/documentation/collect/payment-sessions/authorisation-capture/error-handling Conflict scenarios for capture and void operations ## Conflict scenarios (409) Captures and voids return `409 Conflict` when the payment is not in a valid state for the requested operation: | Operation | Scenario | Error detail | | - | - | - | | Capture | Payment not authorised | `"The payment is not in an authorised state and cannot be captured."` | | Capture | Amount exceeds remaining | `"The capture amount exceeds the remaining authorised amount."` | | Capture | Authorisation expired | `"The authorisation has expired and can no longer be captured."` | | Void | Payment not authorised | `"The payment is not in an authorised state and cannot be voided."` | | Void | Already fully voided | `"The payment has already been fully voided."` | | Void | No remaining authorisation | `"There is no remaining authorised amount to void."` | # Examples Source: https://docs.momentco.io/documentation/collect/payment-sessions/authorisation-capture/examples Common authorisation and capture flow examples ## Common Examples ### E-commerce: authorise at checkout, capture at shipment ``` 1. Customer places order for R1 500.00 2. Create session with capture_method: manual β†’ authorise R1 500.00 3. Webhook: payment.succeeded (funds reserved) 4. Warehouse confirms stock and ships β†’ POST /captures (full amount) 5. Webhook: payment.captured ``` ### Hotel: authorise estimated stay, capture actual amount ``` 1. Guest checks in, estimated 3-night stay at R500/night 2. Authorise R1 500.00 3. Webhook: payment.succeeded (funds reserved) 4. Guest checks out after 2 nights β†’ capture R1 000.00 5. Void remaining authorisation 6. Webhooks: payment.captured, then payment.voided ``` ### Pre-order: authorise, void if unavailable ``` 1. Customer pre-orders limited item for R2 000.00 2. Authorise R2 000.00 3. Webhook: payment.succeeded (funds reserved) 4. Item becomes unavailable β†’ void full amount 5. Webhook: payment.voided ``` ### Partial fulfillment: capture per shipment ``` 1. Customer orders 3 items totalling R3 000.00 2. Authorise R3 000.00 3. Webhook: payment.succeeded (funds reserved) 4. Ship item 1 (R1 200.00) β†’ capture R1 200.00 5. Ship item 2 (R800.00) β†’ capture R800.00 6. Item 3 out of stock β†’ void remaining authorisation 7. Webhooks: payment.captured (x2), payment.voided ``` ### Fraud review: authorise, review, then decide ``` 1. Customer places R5 000.00 order 2. Authorise R5 000.00 3. Webhook: payment.succeeded (funds reserved) 4. Fraud team reviews (within authorisation window) 5a. Approved β†’ capture full amount 5b. Flagged β†’ void full amount ``` ### Tip adjustment: authorise base amount, capture with gratuity ``` 1. Customer finishes meal for R500.00 2. Authorise R500.00 3. Webhook: payment.succeeded (funds reserved) 4. Customer adds R75.00 tip β†’ capture R575.00 (within authorised amount) 5. Webhook: payment.captured ``` ### Rental deposit: authorise deposit, capture actual charges ``` 1. Customer rents a car, R5 000.00 deposit required 2. Authorise R5 000.00 3. Webhook: payment.succeeded (funds reserved) 4. Customer returns car, rental fee is R2 500.00 β†’ capture R2 500.00 5. Void remaining R2 500.00 deposit 6. Webhooks: payment.captured, then payment.voided ``` ## Detailed End-to-End Example A merchant selling electronics wants to authorise at checkout and capture at shipment. **Step 1: Create payment session** ```json theme={"system"} POST /collect/payment_sessions { "amount": 250000, "currency": "ZAR", "type": "one_time", "options": { "customer": { "name": "Lara", "email": "lara@example.com" }, "checkout_options": { "return_url": "https://shop.example/order/ord_789/status" }, "payment_method_options": { "card": { "capture_method": "manual" } } }, "external_reference": "ord_789", "metadata": { "order_id": "ord_789", "items": "laptop,mouse" } } ``` **Step 2: Customer authorises (webhook received)** ```json theme={"system"} { "id": "evt_92847561234", "type": "payment.succeeded", "data": { "id": "pay_Xk9mPqR3sTuV7w", "status": "succeeded", "amount": 250000, "currency": "ZAR", "capture_method": "manual", "authorised_amount": 250000, "paid_amount": 0, "voided_amount": 0, "country": "ZA", "payment_method_details": { "type": "card", "card": { "type": "CREDIT", "scheme": "VISA", "bin": "411111", "last4": "1234" } }, "external_reference": "ord_789", "mode": "live", "metadata": { "order_id": "ord_789", "items": "laptop,mouse" }, "created_at": "2026-03-18T14:30:00.000Z", "updated_at": "2026-03-18T14:30:45.123Z" } } ``` **Step 3: Laptop ships, capture R2 200.00** ```json theme={"system"} POST /collect/payments/pay_Xk9mPqR3sTuV7w/captures Idempotency-Key: capture_ord_789_laptop { "amount": 220000 } ``` **Step 4: Mouse out of stock, void remaining authorisation** ```json theme={"system"} POST /collect/payments/pay_Xk9mPqR3sTuV7w/voids Idempotency-Key: void_ord_789_mouse // No request body needed ``` Final payment state: ```json theme={"system"} { "authorised_amount": 250000, "paid_amount": 220000, "voided_amount": 30000 } ``` # Lifecycle Source: https://docs.momentco.io/documentation/collect/payment-sessions/authorisation-capture/lifecycle Payment lifecycle for authorisation and capture flows Pass `payment_method_options.card.capture_method: manual` inside the session options: ```json theme={"system"} POST /collect/payment_sessions { "amount": 100000, "currency": "ZAR", "type": "one_time", "options": { "checkout_options": { "return_url": "https://merchant.example/order/123/status" }, "payment_method_options": { "card": { "capture_method": "manual" } } }, "external_reference": "order_123" } ``` The customer is redirected to the checkout page (`session_url`) and completes the card payment. On success, the underlying payment object transitions to `status: succeeded`, meaning funds are **reserved but not yet settled**. The Payment Session itself transitions to `status: completed` with `payment_outcome: reserved`. The session outcome remains `reserved` even after subsequent captures or voids, which are tracked on the underlying payment resource. The deprecated `payment_status` field continues to be populated with `paid` for backwards compatibility. The payment object at this stage: ```json theme={"system"} { "id": "pay_rOaZNqKZMFxsRb2NBL1cj", "status": "succeeded", "intent": "payment", "amount": 100000, "currency": "ZAR", "capture_method": "manual", "authorised_amount": 100000, "paid_amount": 0, "voided_amount": 0, "country": "ZA", "payment_method_details": { "type": "card", "card": { "type": "CREDIT", "scheme": "MASTERCARD", "bin": "527346", "last4": "3333" } }, "external_reference": "order_123", "mode": "live", "created_at": "2026-03-18T09:00:44.224Z", "updated_at": "2026-03-18T09:01:12.531Z" } ``` **Webhook fired:** `payment.succeeded` Once the authorisation succeeds, you have a limited window to act before it expires: * **Capture** the payment to settle funds (full or partial) * **Void** the authorisation to release the hold * **Do nothing** and let the authorisation expire automatically ## Flow Diagram ```mermaid theme={"system"} flowchart TD A[Authorisation succeeds] --> B{Merchant decides} B --> C[Full capture] B --> D[Partial capture] B --> E[Void] B --> F[No action] D --> G{Remaining authorisation} G --> H[Capture remaining] G --> I[Void remaining] F --> J[Authorisation expires] ``` ## Authorisation Expiry Authorisations have a limited validity period set by the issuing bank and card network. After expiry: * The authorisation can no longer be captured * Reserved funds are automatically released back to the customer by the card network * Attempting to capture returns a `409 Conflict` error: ```json theme={"system"} { "type": "https://docs.momentco.io/errors/conflict", "title": "Conflict", "status": 409, "detail": "The authorisation has expired and can no longer be captured.", "instance": "/collect/payments/pay_rOaZNqKZMFxsRb2NBL1cj/captures", "code": "conflict" } ``` Authorisation validity periods vary by payment method and issuing bank, typically ranging from 5 to 30 days for card payments. Plan your capture workflow accordingly. # Overview Source: https://docs.momentco.io/documentation/collect/payment-sessions/authorisation-capture/overview Introduction to authorisation and capture payments Authorisation and capture is a two-step payment flow that allows merchants to reserve funds on a customer's payment method before settling the transaction. It separates the moment of commitment from the moment of collection: authorisation confirms that funds are available and places a hold, while capture moves the money. This flow applies only to card payments. It is initiated by setting `capture_method: manual` when creating a payment session. If omitted, `capture_method` defaults to `automatic`, which settles funds immediately upon authorisation (standard sale behaviour). When `capture_method: manual` is set on a session that goes through Moment Checkout, the checkout presents card payment methods only. Non-card options are not surfaced to the customer, so a manual-capture session always results in a card payment.
See when authorisation and capture is the right fit for your payment flow. Understand the states a payment moves through from authorisation to settlement or expiry. Learn how to capture or void authorised payments, including partial captures. React to authorisation, capture, and void events in real time. Handle conflict scenarios for capture and void operations. Common authorisation and capture flow examples with full request and response payloads.
# Use Cases Source: https://docs.momentco.io/documentation/collect/payment-sessions/authorisation-capture/use-cases When to use authorisation and capture Reserve funds when a customer places an order, then capture when the item ships. If the item becomes unavailable, void the authorisation instead. ```json theme={"system"} { "amount": 150000, "currency": "ZAR", "type": "one_time", "options": { "checkout_options": { "return_url": "https://merchant.example/return" }, "payment_method_options": { "card": { "capture_method": "manual" } } } } ``` Authorise the estimated amount (e.g. hotel stay), then capture the actual final amount. Void the remaining authorisation. For example, authorise R1 500.00 for a 3-night hotel stay. If the guest checks out after 2 nights (R1 000.00), capture R1 000.00 and void the remaining authorisation. Authorise immediately, then review the transaction asynchronously. This gives your fraud team a window to review high-value or suspicious transactions before funds are settled. Capture after approval or void if flagged. Authorise the full order amount, then capture per shipment as items become available. Void the remaining authorisation for any unshipped items. For example, a customer orders 3 items totalling R3 000.00. Ship item 1 (R1 200.00) and capture that amount. Ship item 2 (R800.00) and capture again. If item 3 is out of stock, void the remaining authorisation. Authorise the base amount (e.g. a restaurant bill), then capture the final amount including the tip. Void the difference if the tip is less than the authorised buffer. For example, authorise R500.00 for a meal. The customer adds a R75.00 tip, so you capture R575.00 (if within the authorised amount) or capture R500.00 and process the tip separately. Authorise a deposit amount when a customer begins a rental (e.g. car hire, equipment rental), then capture only the actual usage charges and void the remainder. For example, authorise R5 000.00 for a weekend car rental. The customer returns the car with no damage, so you capture R2 500.00 for the rental fee and void the remaining R2 500.00 deposit. For simple, immediate payments where no review or delay is needed, use the default `capture_method: automatic`. # Webhooks Source: https://docs.momentco.io/documentation/collect/payment-sessions/authorisation-capture/webhooks Webhook events for authorisation and capture flows The following webhook events are emitted during the authorisation and capture lifecycle: * **`payment.succeeded`** Fired when the authorisation succeeds. The payment `status` is `succeeded`, `authorised_amount` is set, and `paid_amount` is `0`. [See `payment.succeeded` Event](/api-reference/collect/payments/webhook/succeeded) * **`payment.captured`** Fired when a capture succeeds (full or partial). The `paid_amount` field reflects the newly captured total. [See `payment.captured` Event](/api-reference/collect/payments/webhook/captured) * **`payment.voided`** Fired when a void succeeds. The `voided_amount` field is incremented by the released amount while `authorised_amount` remains unchanged. [See `payment.voided` Event](/api-reference/collect/payments/webhook/voided) * **`payment.attempt_failed`** Fired when an authorisation attempt fails. The `last_payment_error` field contains error details. [See `payment.attempt_failed` Event](/api-reference/collect/payments/webhook/attempt_failed) * **`payment.failed`** Fired when all payment attempts are exhausted and the payment has failed terminally. The `last_payment_error` field contains details of the final failed attempt. [See `payment.failed` Event](/api-reference/collect/payments/webhook/failed) See the [Payments API Overview](/api-reference/collect/payments/overview) for the full list of webhook events and error codes. # Lifecycle Source: https://docs.momentco.io/documentation/collect/payment-sessions/lifecycle Payment session statuses and state transitions Each session type moves through its own set of `status` values over its lifecycle. The diagram, states, and transitions are described below for each session type. ## One Time and First In Series Payment Sessions Both are customer-initiated, customer-present flows: the customer is present and completes payment via a hosted Checkout page. ```mermaid theme={"system"} stateDiagram-v2 direction LR [*] --> active : Session Created state active { a_not_started: not_started a_pending: pending a_unpaid: unpaid } note left of active A failed attempt stays active for retry, while attempts remain. end note state completed { c_reserved: reserved c_paid: paid c_unpaid: unpaid c_pmv: payment_method_verified } state expired { e_not_started: not_started e_unpaid: unpaid } state cancelled { x_not_started: not_started x_unpaid: unpaid } active --> completed : Payment succeeded, verification succeeded, or attempts exhausted active --> expired : Timeout or inactivity active --> cancelled : Explicit cancellation completed --> [*] expired --> [*] cancelled --> [*] ``` The session remains in an **active** state while the customer is interacting with the Checkout page via the `session_url`. In the event of a failed payment, the session remains in an **active** state to allow the customer to make another payment attempt, as long as further attempts are still permitted. In the event of a successful payment, the session transitions into a **completed** state. While the session is **active**, the payment outcome (`payment_outcome`) is always one of: **not\_started** (no attempt has been made yet), **pending** (an attempt is in flight), or **unpaid** (the most recent attempt failed, and another attempt is still permitted). The session has **completed**. This does not necessarily mean the payment was successful. The outcome must be verified by interrogating the payment outcome (`payment_outcome`). A **completed** session's payment outcome (`payment_outcome`) is one of: **reserved** (an auth-only hold was placed and is awaiting capture), **paid** (the payment was captured and the session closed successfully), **unpaid** (the session closed without payment because all permitted attempts were exhausted), or **payment\_method\_verified** (the payment method was saved via an `amount: 0` verification flow). If the outcome is **paid**, **reserved**, or **payment\_method\_verified**, the `payment_id` field represents the associated payment; **reserved** and **payment\_method\_verified** both execute a payment without moving funds. The session **expired** due to inactivity or a timeout. The customer did not complete the checkout process in time. An **expired** session's payment outcome (`payment_outcome`) is either **not\_started** (the session timed out before any payment attempt was made) or **unpaid** (the session timed out after a payment attempt had already failed). The session was explicitly **cancelled** by the customer or merchant before the payment completed. A **cancelled** session's payment outcome (`payment_outcome`) is either **not\_started** (cancelled before any payment attempt was made) or **unpaid** (cancelled after a payment attempt had already failed). | **From State** | **To State** | **Condition** | **Description** | | - | - | - | - | | `active` | `completed` | Payment succeeded, payment method verification succeeded, or all permitted attempts were exhausted without success. | The payment outcome (`payment_outcome`) reflects the result: **paid** or **reserved** on payment success, **payment\_method\_verified** on verification success, **unpaid** once attempts are exhausted. | | `active` | `expired` | The session timed out due to inactivity. | The session expired because the customer didn't complete the payment or the authorization timed out. | | `active` | `cancelled` | The session was explicitly cancelled by the customer or merchant. | The session was cancelled during checkout or before the programmatic payment was processed. | The table below is the complete reference: every `status` and `payment_outcome` combination that can occur for a `one_time` or `first_in_series` session. | State | Payment Outcome (`payment_outcome`) | Description | | - | - | - | | `active` | `not_started` | Session created, no payment attempt yet. | | `active` | `pending` | Payment initiated, awaiting confirmation. | | `active` | `unpaid` | Payment failed, session still open (retry possible). | | `completed` | `reserved` | Auth-only hold placed, capture pending. | | `completed` | `paid` | Payment captured, session closed successfully. | | `completed` | `unpaid` | Session closed without payment (e.g. all attempts exhausted). | | `completed` | `payment_method_verified` | Payment method saved successfully for a customer-present (CIT) payment. | | `expired` | `not_started` | Session timed out before any payment attempt. | | `expired` | `unpaid` | Session timed out after a payment failure. | | `cancelled` | `not_started` | Cancelled before any payment attempt. | | `cancelled` | `unpaid` | Cancelled after a payment failure. | ## Next In Series Payment Sessions A merchant-initiated, customer-absent flow: the customer is not present, and the payment is processed programmatically against an existing mandate. ```mermaid theme={"system"} stateDiagram-v2 direction LR [*] --> active : Session Created state active { a_not_started: not_started a_pending: pending } state completed { c_paid: paid c_unpaid: unpaid c_reserved: reserved } active --> completed : Payment succeeded or failed completed --> [*] ``` The session is briefly in an **active** state while the payment is processed without customer interaction, using a saved payment method. In the event of either a successful or a failed payment, the session transitions into a **completed** state. While the session is **active**, the payment outcome (`payment_outcome`) is always one of: **not\_started** (no attempt has been made yet) or **pending** (an attempt is in flight). The session has **completed**, though this doesn't necessarily mean the payment succeeded; check `payment_outcome`. It is one of: **paid** (captured successfully), **unpaid** (the payment failed), or **reserved** (auth-only hold, capture pending). `payment_method_verified` never applies, since payment method verification isn't available on `next_in_series` sessions. If the outcome is **paid** or **reserved**, the `payment_id` field represents the associated payment. | **From State** | **To State** | **Condition** | **Description** | | :- | :- | :- | :- | | `active` | `completed` | Payment succeeded or failed. | The session always completes once the payment is processed. The payment outcome (`payment_outcome`) reflects the result: **paid** or **reserved** on success, **unpaid** on failure. | The table below is the complete reference: every `status` and `payment_outcome` combination that can occur for a `next_in_series` session. | State | Payment Outcome (`payment_outcome`) | Description | | - | - | - | | `active` | `not_started` | Session created, no payment attempt yet. | | `active` | `pending` | Payment initiated, awaiting confirmation. | | `completed` | `paid` | Payment captured, session closed successfully. | | `completed` | `reserved` | Auth-only hold placed, capture pending. | | `completed` | `unpaid` | Payment failed, session closed without payment. | `payment_status` is deprecated in favour of `payment_outcome`. The new field supersedes the old and adds `not_started`, `reserved`, and `payment_method_verified` values. The deprecated field is still populated with `paid`, `unpaid`, or `pending` for backwards compatibility. # Consent Source: https://docs.momentco.io/documentation/collect/payment-sessions/mandates/consent Configuring payment consent boundaries for recurring payments A **consent** defines what the customer is agreeing to and the boundaries within which the merchant may collect future payments. It is captured during a `first_in_series` payment session by passing `mandate_options`, and becomes the durable record of customer authorisation that governs every subsequent `next_in_series` payment. Providing `mandate_options` is optional. Omitting it entirely creates an unconstrained mandate: valid indefinitely, for any amount, any number of times, with no declared intent. A consent is made up of the mandate type, amount, recurrence, validity period, and maximum number of payments. Each is covered below. ### Mandate Type The `type` field declares the kind of mandate the customer is consenting to. #### Scheduled mandates The customer consents to collection on a fixed cadence, defined by `recurrence`. ```json theme={"system"} { "mandate_options": { "type": "scheduled", "recurrence": { "type": "monthly", "interval_count": 1 } } } ``` Payment methods whose consent surface displays an interval (for example, Capitec VRP) require `scheduled` or `installment`.
#### On-demand mandates The customer consents to collection without a fixed cadence. Use for wallet top-ups, card-on-file ad-hoc charges, or any flow where the customer has not agreed to a rhythm. ```json theme={"system"} { "mandate_options": { "type": "on_demand" } } ``` Payment methods that require a cadence at consent (for example, Capitec VRP) are not eligible on `on_demand`. Use `scheduled` instead.
#### Installment mandates The customer consents to a fixed payment plan: a defined number of payments totalling `total_amount`. The structure is set by `terms`: * **`periodic`**: equal payments on a fixed cadence. `recurrence` and `max_occurrences` are both required inside `terms`. * **`fixed`**: an explicit schedule with specific amounts and due dates per installment. Use when payment amounts vary or dates are irregular. A 4-payment monthly installment plan totalling 1000.00 ZAR: ```json theme={"system"} { "mandate_options": { "type": "installment", "total_amount": 100000, "terms": { "type": "periodic", "recurrence": { "type": "monthly", "interval_count": 1 }, "max_occurrences": 4 } } } ``` A 3-payment fixed plan with a larger final payment: ```json theme={"system"} { "mandate_options": { "type": "installment", "total_amount": 100000, "terms": { "type": "fixed", "items": [ { "amount": 30000, "due_date": "2026-04-01" }, { "amount": 30000, "due_date": "2026-05-01" }, { "amount": 40000, "due_date": "2026-06-01" } ] } } } ```
### Amount Defines the amount the customer consents to in future payments. **Fixed amount**: the exact amount that may be collected each time: ```json theme={"system"} { "mandate_options": { "type": "scheduled", "amount": 2000 } } ``` **Amount range**: a minimum and/or maximum the payment may fall within: ```json theme={"system"} { "mandate_options": { "type": "on_demand", "amount": { "min": 1000, "max": 5000 } } } ``` If not specified, the `amount` from the originating `first_in_series` session is used as the consent ceiling and applies to all subsequent `next_in_series` payments under the mandate.
### Recurrence Defines the consented payment cadence: how frequently the customer has agreed to be charged. Required for `type: scheduled`. Not available on `type: on_demand` (no cadence is consented). For `type: installment`, recurrence is set inside `terms`. Payments every month on the 1st: ```json theme={"system"} { "mandate_options": { "type": "scheduled", "recurrence": { "type": "monthly", "interval_count": 1, "on": { "type": "day_of_month", "days": [1] } } } } ``` Payments every month: ```json theme={"system"} { "mandate_options": { "type": "scheduled", "recurrence": { "type": "monthly", "interval_count": 1 } } } ``` Payments every week on Mondays: ```json theme={"system"} { "mandate_options": { "type": "scheduled", "recurrence": { "type": "weekly", "interval_count": 1, "on": { "days": ["mon"] } } } } ``` Payments every week: ```json theme={"system"} { "mandate_options": { "type": "scheduled", "recurrence": { "type": "weekly", "interval_count": 1 } } } ```
### Validity Period Restricts the mandate to a specific date range. Payments outside this range will be rejected. A mandate valid throughout 2026: ```json theme={"system"} { "mandate_options": { "type": "on_demand", "validity_period": { "start_date": "2026-01-01", "end_date": "2026-12-31" } } } ``` If `start_date` is omitted, the mandate is active immediately from the date of creation. If `end_date` is omitted, the mandate does not expire. All date boundaries are evaluated in UTC.
### Max Occurrences Caps the total number of successful payments allowed under the mandate. A mandate limited to 12 successful payments in total: ```json theme={"system"} { "mandate_options": { "type": "scheduled", "max_occurrences": 12 } } ``` If not specified, occurrences are unlimited. *** For full configuration examples, see [Mandates: Examples](/documentation/collect/payment-sessions/mandates/examples). For the complete schema, see the [API Reference](/api-reference/collect/payment_sessions/quickstart-recurring). # Controls Source: https://docs.momentco.io/documentation/collect/payment-sessions/mandates/controls Period limits, spacing, day restrictions, and retry configuration for mandates Controls are optional `mandate_options` fields that govern how and when payments are executed under a mandate. They sit alongside the consent bounds the customer agreed to but do not change them. Use them when you need to enforce period caps, restrict payment timing, or define retry behaviour after a failure. These fields are ceilings and constraints, not instructions for when or how often to collect. All constraints are cumulative: a payment must satisfy every active constraint simultaneously. The platform will not execute a payment that would violate any one of them.
### Period Limits Applies rolling caps within a defined time window, useful for preventing over-collection within a billing period. Period limits apply to `scheduled` and `on_demand` mandates. They do not apply to `installment` mandates, where `total_amount` and `terms.max_occurrences` serve as the aggregate consent ceiling. No more than 2 payments per month, capped at 500.00 ZAR total within the window: ```json theme={"system"} { "mandate_options": { "type": "on_demand", "period_limits": { "period": "month", "max_count": 2, "max_amount": 50000 } } } ``` You can also control how the window is aligned using the `window` field. By default, windows align to calendar boundaries (e.g. the 1st of each month). Use `cycle` mode with an anchor to align to a specific date instead. The same limits, with the monthly window resetting on the 15th: ```json theme={"system"} { "mandate_options": { "type": "scheduled", "period_limits": { "period": "month", "max_count": 2, "max_amount": 50000, "window": { "mode": "cycle", "anchor": { "type": "day_of_month", "day": 15 } } } } } ``` When `period_limits.max_amount` is set, it must be greater than or equal to `amount.max`, since it represents a cumulative ceiling across multiple payments within the window. Setting `max_amount` lower than the per-payment cap is a contradiction and will be rejected. **Provider mapping behavior** When `period_limits.max_amount` is set, providers receive it as the collection ceiling for the period. The platform enforces the per-payment `amount.max` constraint independently before submitting to the provider. When `period_limits` is not set on a `scheduled` mandate, some providers derive the collection ceiling from the consented `amount` (fixed) or `amount.max` (variable), using the `recurrence` interval as the implicit period window. On `on_demand` mandates there is no recurrence, so providers derive the ceiling from `amount` only with no period anchor; set `period_limits` explicitly when a per-period cap is needed. `validity_period` and `period_limits` are independent constraints, both evaluated separately and both must pass for a payment to be permitted. `validity_period` controls whether the mandate is active at all. `period_limits` controls how much or how often within a window. Neither overrides the other. Calendar windows are fixed to calendar boundaries regardless of when the mandate becomes active. The window does not start from the mandate active date. The first period window may be partial. A mandate active from March 28th with `max_count: 1` per month permits one charge in the remainder of March and another on April 1st, just 4 days later, because they fall in different calendar windows. This applies even when `start_date` is omitted, since the mandate becomes active on the day of creation. The same applies in cycle mode. A mandate active from March 28th with a cycle anchor on the 15th runs its first window from March 15th to April 14th. Charges before March 28th are blocked by the validity constraint, but the window itself started on the 15th, so the usable portion of the first window is only March 28th to April 14th. Set `validity_period.start_date` explicitly and align it with the period boundary. This ensures the mandate becomes active exactly when the first window opens, giving the integrator and the customer a clean, predictable first period. **Calendar mode: start date aligned to the 1st of the month** `start_date` is set to April 1st, which coincides with the calendar month boundary. The first window runs from April 1st to April 30th in full: ```json theme={"system"} { "mandate_options": { "type": "scheduled", "validity_period": { "start_date": "2026-04-01" }, "period_limits": { "period": "month", "max_count": 2, "max_amount": 50000 } } } ``` **Cycle mode: start date aligned to the anchor day** `start_date` is set to April 15th, matching the cycle anchor. The first window runs from April 15th to May 14th in full: ```json theme={"system"} { "mandate_options": { "type": "scheduled", "validity_period": { "start_date": "2026-04-15" }, "period_limits": { "period": "month", "max_count": 2, "max_amount": 50000, "window": { "mode": "cycle", "anchor": { "type": "day_of_month", "day": 15 } } } } } ```
### Spacing Controls the minimum and maximum number of calendar days allowed between successful payments. Useful for preventing payments from landing too close together or drifting too far apart. Spacing applies to `on_demand` mandates. Payments no sooner than 7 days apart and no later than 31 days apart: ```json theme={"system"} { "mandate_options": { "type": "on_demand", "spacing": { "min_interval_days": 7, "max_interval_days": 31 } } } ``` If not specified, no spacing constraints are enforced.
### Allowed Days Restricts payments to specific days within a given time scope. Use this on `on_demand` mandates when collections must land on predictable, well-defined dates (for example, always on the 1st and 15th of the month, every Monday, or on a specific date each year). For `scheduled` mandates, use `recurrence.on` instead. Four constraint types are supported: **Day of month** (monthly billing): payments only on specific calendar days each month. Payments permitted on the 1st and 15th of each month, adjusted to the nearest weekday if they fall on a weekend: ```json theme={"system"} { "mandate_options": { "type": "on_demand", "allowed_days": { "type": "day_of_month", "days": [1, 15], "adjustment": "nearest_weekday" } } } ``` **Day of week** (weekly billing): payments only on specific days of the week. Payments permitted on Mondays, Wednesdays, and Fridays only: ```json theme={"system"} { "mandate_options": { "type": "on_demand", "allowed_days": { "type": "day_of_week", "days": ["mon", "wed", "fri"] } } } ``` **Day of year** (annual billing): payments only on specific dates each year. Payments permitted on January 15th each year: ```json theme={"system"} { "mandate_options": { "type": "on_demand", "allowed_days": { "type": "day_of_year", "dates": [ { "month": 1, "day": 15 } ] } } } ``` Multiple dates are also supported. For example, to allow payments on both January 15th and July 1st each year: ```json theme={"system"} { "mandate_options": { "type": "on_demand", "allowed_days": { "type": "day_of_year", "dates": [ { "month": 1, "day": 15 }, { "month": 7, "day": 1 } ] } } } ``` **Nth day of month** (specific occurrence billing): payments only on a specific day occurrence each month. Payments permitted on the second Monday of each month: ```json theme={"system"} { "mandate_options": { "type": "on_demand", "allowed_days": { "type": "nth_day_of_month", "day": "mon", "occurrence": 2 } } } ``` The `adjustment` field on `day_of_month` and `day_of_year` controls what happens when a scheduled day falls on a weekend. Defaults to `none` if not specified: * `nearest_weekday`: shifts to the closest weekday, either forward or backward. * `next_weekday`: always shifts forward to the next Monday. * `previous_weekday`: always shifts backward to the preceding Friday. * `none *(default)*`: no adjustment is applied. The payment is scheduled on the weekend day as defined.
### Recovery These fields define how the merchant may handle failed payments, controlling retry behaviour under the mandate.
### Retry Policy Defines how failed collections may be retried via new `next_in_series` payments. Retries are business retries, not automatic re-attempts, but explicit new payment sessions initiated by the merchant at a later time. Retry policy applies to `scheduled`, `on_demand`, and `installment` mandates. Up to 3 retries, at least 3 days apart, permitted within 30 days of the original failure: ```json theme={"system"} { "mandate_options": { "type": "on_demand", "retry_policy": { "max_retries": 3, "min_days_between_retries": 3, "max_days_since_failure": 30 } } } ``` * `max_retries`: maximum number of retry attempts after a failed payment. If not specified, unlimited. * `min_days_between_retries`: minimum gap between retry attempts. If not specified, no minimum is enforced. * `max_days_since_failure`: how long after the original failure retries are still permitted. If not specified, retries may occur indefinitely.
# Examples Source: https://docs.momentco.io/documentation/collect/payment-sessions/mandates/examples Common mandate configurations The examples below show `mandate_options` configuration fragments for common billing patterns. They are passed as part of a `first_in_series` payment session request. See [Quickstart: Recurring Payments](/api-reference/collect/payment_sessions/quickstart-recurring) for the full end-to-end flow. *** ### Common Examples #### Daily billing A mandate authorising a fixed daily payment: ```json theme={"system"} { "mandate_options": { "type": "scheduled", "amount": 500, "validity_period": { "start_date": "2026-01-01", "end_date": "2026-12-31" }, "recurrence": { "type": "daily", "interval_count": 1 } } } ``` #### Weekly billing A mandate authorising one payment per week: ```json theme={"system"} { "mandate_options": { "type": "scheduled", "amount": 1000, "validity_period": { "start_date": "2026-01-01", "end_date": "2026-12-31" }, "recurrence": { "type": "weekly", "interval_count": 1 } } } ``` A mandate authorising one payment per week on Mondays: ```json theme={"system"} { "mandate_options": { "type": "scheduled", "amount": 1000, "validity_period": { "start_date": "2026-01-01", "end_date": "2026-12-31" }, "recurrence": { "type": "weekly", "interval_count": 1, "on": { "days": ["mon"] } } } } ``` #### Monthly billing A mandate authorising one payment per month: ```json theme={"system"} { "mandate_options": { "type": "scheduled", "amount": 2000, "validity_period": { "start_date": "2026-01-01", "end_date": "2026-12-31" }, "recurrence": { "type": "monthly", "interval_count": 1 } } } ``` A mandate authorising one payment per month on the 1st: ```json theme={"system"} { "mandate_options": { "type": "scheduled", "amount": 2000, "validity_period": { "start_date": "2026-01-01", "end_date": "2026-12-31" }, "recurrence": { "type": "monthly", "interval_count": 1, "on": { "type": "day_of_month", "days": [1], "adjustment": "nearest_weekday" } } } } ``` #### Quarterly billing A mandate authorising one payment per quarter: ```json theme={"system"} { "mandate_options": { "type": "scheduled", "amount": 6000, "validity_period": { "start_date": "2026-01-01", "end_date": "2026-12-31" }, "recurrence": { "type": "monthly", "interval_count": 3 } } } ``` A mandate authorising one payment per quarter on the 1st of the month: ```json theme={"system"} { "mandate_options": { "type": "scheduled", "amount": 6000, "validity_period": { "start_date": "2026-01-01", "end_date": "2026-12-31" }, "recurrence": { "type": "monthly", "interval_count": 3, "on": { "type": "day_of_month", "days": [1], "adjustment": "nearest_weekday" } } } } ``` #### Annual billing A mandate authorising one payment per year: ```json theme={"system"} { "mandate_options": { "type": "scheduled", "amount": 24000, "validity_period": { "start_date": "2026-01-01", "end_date": "2028-12-31" }, "recurrence": { "type": "yearly", "interval_count": 1 } } } ``` A mandate authorising one payment per year on January 1st: ```json theme={"system"} { "mandate_options": { "type": "scheduled", "amount": 24000, "validity_period": { "start_date": "2026-01-01", "end_date": "2028-12-31" }, "recurrence": { "type": "yearly", "interval_count": 1, "on": { "type": "day_of_year", "dates": [ { "month": 1, "day": 1 } ] } } } } ``` #### On-demand (card top-up) A mandate authorising merchant-initiated collections of up to 50.00 ZAR: ```json theme={"system"} { "mandate_options": { "type": "on_demand", "amount": { "max": 5000 }, "validity_period": { "start_date": "2026-01-01", "end_date": "2026-12-31" } } } ``` A mandate authorising merchant-initiated collections of up to 50.00 ZAR, no more than 10 times per month, capped at 200.00 ZAR monthly: ```json theme={"system"} { "mandate_options": { "type": "on_demand", "amount": { "max": 5000 }, "validity_period": { "start_date": "2026-01-01", "end_date": "2026-12-31" }, "period_limits": { "period": "month", "max_count": 10, "max_amount": 20000 } } } ``` `on_demand` mandates do not carry a recurrence. Payment methods whose consent surface requires an interval (such as Capitec VRP) are not eligible on `on_demand`. See the next example for the VRP equivalent of this pattern. #### On-demand (Capitec VRP top-up) Capitec VRP does not support true on-demand collection; its consent surface always requires a cadence. A `scheduled` mandate with a daily `recurrence` approximates it instead, authorising merchant-initiated collections of up to 50.00 ZAR on any day: ```json theme={"system"} { "mandate_options": { "type": "scheduled", "amount": { "max": 5000 }, "recurrence": { "type": "daily", "interval_count": 1 }, "validity_period": { "start_date": "2026-01-01", "end_date": "2026-12-31" } } } ``` See [Recurring Payments](/documentation/collect/payment-sessions/recurring-payments/overview) for how mandate type and payment method eligibility fit together. #### Installment (periodic) A mandate for 4 equal monthly payments of 250.00 ZAR, totalling 1000.00 ZAR: ```json theme={"system"} { "mandate_options": { "type": "installment", "total_amount": 100000, "terms": { "type": "periodic", "recurrence": { "type": "monthly", "interval_count": 1, "on": { "type": "day_of_month", "days": [1], "adjustment": "nearest_weekday" } }, "max_occurrences": 4, "amount": 25000 }, "validity_period": { "start_date": "2026-04-01", "end_date": "2026-07-31" }, "timezone": "Africa/Johannesburg", "retry_policy": { "max_retries": 2, "min_days_between_retries": 3, "max_days_since_failure": 14 } } } ``` #### Installment (fixed) A mandate for 3 payments on specific dates with a larger final payment, totalling 1000.00 ZAR: ```json theme={"system"} { "mandate_options": { "type": "installment", "total_amount": 100000, "terms": { "type": "fixed", "adjustment": "nearest_weekday", "items": [ { "amount": 30000, "due_date": "2026-04-01" }, { "amount": 30000, "due_date": "2026-05-01" }, { "amount": 40000, "due_date": "2026-06-01" } ] }, "validity_period": { "start_date": "2026-04-01", "end_date": "2026-06-30" }, "timezone": "Africa/Johannesburg", "retry_policy": { "max_retries": 2, "min_days_between_retries": 3, "max_days_since_failure": 14 } } } ``` Some providers require a declared collection interval as part of consent registration. For `fixed` terms, add a `recurrence` hint to `terms` to satisfy this requirement. It has no scheduling role: payment dates are still defined by `items`. The platform derives `amount.max` for provider registration from the largest amount across the installment items. ```json theme={"system"} { "mandate_options": { "type": "installment", "total_amount": 100000, "terms": { "type": "fixed", "recurrence": { "type": "monthly", "interval_count": 1 }, "adjustment": "nearest_weekday", "items": [ { "amount": 30000, "due_date": "2026-04-01" }, { "amount": 30000, "due_date": "2026-05-01" }, { "amount": 40000, "due_date": "2026-06-01" } ] } } } ```
### Complete Example A mandate for a monthly subscription capped at 12 payments over one year: ```json theme={"system"} { "mandate_options": { "type": "scheduled", "amount": 2000, "validity_period": { "start_date": "2026-01-01", "end_date": "2026-12-31" }, "max_occurrences": 12, "recurrence": { "type": "monthly", "interval_count": 1 } } } ``` A mandate for a monthly subscription capped at 12 payments over one year, scheduled on the 1st and 15th of each month, with no more than one payment per month, and up to 3 retries within 30 days of a failure: ```json theme={"system"} { "mandate_options": { "type": "scheduled", "amount": 2000, "validity_period": { "start_date": "2026-01-01", "end_date": "2026-12-31" }, "max_occurrences": 12, "timezone": "Africa/Johannesburg", "recurrence": { "type": "monthly", "interval_count": 1, "on": { "type": "day_of_month", "days": [1, 15], "adjustment": "nearest_weekday" } }, "period_limits": { "period": "month", "max_count": 1, "max_amount": 2000 }, "retry_policy": { "max_retries": 3, "min_days_between_retries": 3, "max_days_since_failure": 30 } } } ``` # Lifecycle Source: https://docs.momentco.io/documentation/collect/payment-sessions/mandates/lifecycle The lifecycle of a mandate from consent to termination A mandate is the merchant's standing permission to collect from a customer. Like any consent, it has a beginning, a middle, and an end. ```mermaid theme={"system"} stateDiagram-v2 [*] --> DRAFT : Mandate created DRAFT --> ACTIVE : Customer authorises consent ACTIVE --> EXPIRED : Validity period ends or max_occurrences reached ACTIVE --> REVOKED : Customer revokes consent EXPIRED --> [*] REVOKED --> [*] ``` | Status | What it means | | - | - | | `DRAFT` | The customer has been offered the consent, but hasn't yet authorised it. The merchant cannot collect. | | `ACTIVE` | The customer has authorised the consent and their payment method is saved. The merchant can now collect against it. | | `EXPIRED` | The consented time window has passed or the maximum number of consented collections has been used. No further collections are allowed. | | `REVOKED` | The customer has revoked their consent (e.g. via their bank app). No further collections are allowed. | ## Stages The merchant initiates the recurring consent by creating a payment session with `mandate_options` describing what the customer is consenting to: how much, how often, until when, and up to how many times. The customer is shown those terms during checkout. At this point the mandate exists in the system but is `DRAFT` β€” it cannot be collected against yet. This is also the only step where the customer is involved; every collection from here on runs in the background. See [Quickstart: Recurring Payments](/api-reference/collect/payment_sessions/quickstart-recurring) for the full request and response, and [Consent](/documentation/collect/payment-sessions/mandates/consent) for the full set of `mandate_options` fields. The customer is taken to the hosted checkout, reviews the consent, and authorises it. The first payment runs as part of the same checkout flow, and the customer's payment method is saved for future use. Once both succeed, the mandate becomes `ACTIVE` and is ready to be used for future collections. Every subsequent collection is initiated by the merchant against the existing mandate, with no involvement from the customer. Before any money moves, the platform checks that the collection falls within everything the customer originally consented to β€” the right amount, within the validity window, and not over the consented number of collections. See [Use Cases](/documentation/collect/payment-sessions/mandates/use-cases) for the most common configurations and the non-obvious behaviours each one can exhibit (weekends, short months, leap years). If the check passes, the payment runs and the mandate is updated to record it. If that update reveals the mandate has reached its cap or its time window has just closed, the mandate is marked as `EXPIRED` in the same flow. If the check fails, the collection is rejected and no payment is taken. **Webhook fired (per successful collection):** `payment.succeeded` A mandate becomes `EXPIRED` when either of two limits is reached: * the validity period has ended (today is past the `end_date` the customer consented to), or * the maximum number of successful collections has been used. Once `EXPIRED`, no further collections are allowed against it. The customer keeps control: they can never be charged more than they originally consented to, in time or in count. A mandate becomes `REVOKED` when the customer cancels their consent outside of the platform β€” for example, by revoking it directly in their bank app (such as the Capitec app for VRP mandates). Once `REVOKED`, any further collection attempt is rejected. This transition is initiated by the customer, not the merchant. ## How the constraints are enforced during payment collection Every payment collection (`next_in_series` ) is checked against everything the customer consented to. 1. If it passes, the payment runs and the mandate is updated. 2. If the consented limit has just been reached, the mandate is marked `EXPIRED` in the same flow. 3. If it fails, the collection is rejected and no payment is taken.
```mermaid theme={"system"} flowchart TD A([next_in_series session]) --> B{Validate against
mandate boundaries} B -- pass --> C[Payment executed] B -- fail --> D[Session rejected] C --> E[Mandate updated] E -- max_occurrences or
end_date reached --> F[Mandate EXPIRED] G([Customer revokes consent
e.g. in bank app]) --> H[Mandate REVOKED] classDef start fill:#E8E4FF,stroke:#7C6AE0,stroke-width:1px,color:#1F1A4A; classDef ok fill:#DFF5E4,stroke:#3FA85A,stroke-width:1px,color:#0E3B1B; classDef rejected fill:#FFE6E2,stroke:#D9534F,stroke-width:1px,color:#5A1A17; classDef terminal fill:#F0E2BA,stroke:#B58A2B,stroke-width:1px,color:#3D2A06; class A,G start; class C ok; class D rejected; class F,H terminal; ```
## Why a collection might be rejected Every collection is checked against the consent the customer authorised. If something is off, the collection is rejected with one of the error codes below. The merchant sees the code in the API response; the customer never sees a failed charge on their statement because nothing was attempted at the bank. | Error | What it means in plain English | | - | - | | `mandate_not_yet_started` | The merchant tried to collect before the consented start date. | | `mandate_expired` | The consent's time window has passed; no further collections are allowed. | | `mandate_max_payments_reached` | The maximum number of consented collections has already been used. | | `mandate_already_used` | This particular payment has already been processed under the mandate (typically a retry safeguard). | | `mandate_amount_out_of_range` | The amount falls outside the range the customer consented to. | | `mandate_amount_exceeds_limit` | The amount is above the maximum the customer consented to. | | `mandate_recurrence_day_not_allowed` | The collection isn't happening on one of the days the customer consented to (e.g. wrong weekday). | | `invalid_mandate_status` | The mandate isn't in a state where collections are allowed (e.g. still in draft, or already expired). | | `mandate_revoked` | The customer has revoked their consent; no further collections are allowed. | # Overview Source: https://docs.momentco.io/documentation/collect/payment-sessions/mandates/overview Introduction to Mandates A **mandate** is the consent a customer provides for a merchant to initiate future charges, without the customer being present in the flow. It is organised around two concerns. * **Consent** defines what the customer has agreed to: the amount, recurrence cadence, validity period, and maximum number of payments. For installment mandates, consent is structured around a total amount and a payment plan. * **Controls** are optional fields that govern how and when payments execute under that consent, without changing what the customer consented to.
Define what the customer is agreeing to: amount, recurrence, validity period, and mandate type. Govern how and when payments execute under a mandate without changing customer consent. Common scenarios, such as subscriptions, debit orders, VRP top-ups, and usage-based billing. From customer consent through collection to termination: expired, exhausted, or cancelled. See which payment methods support mandate-based collections. Full mandate configuration examples for common recurring payment scenarios.
# Supported Payment Methods Source: https://docs.momentco.io/documentation/collect/payment-sessions/mandates/supported-payment-methods How mandate options apply across supported payment methods for recurring payments Different payment methods interact with `mandate_options` and the recurring payment flow in different ways. This page documents the behaviour for each supported payment method across `first_in_series` and `next_in_series` sessions. ### Eligibility by mandate type Cards support every mandate type. Capitec VRP requires a consented cadence, so it works only with `scheduled` or `installment`. | Mandate type | Cards | Capitec VRP | | - | - | - | | `scheduled` | Yes | Yes | | `installment` | Yes | Yes | | `on_demand` | Yes | No (no consented cadence) | While Capitec VRP does not support on-demand collection, a `scheduled` mandate with a daily `recurrence` can be used to obtain consent for collection on any day of the month. ### `first_in_series` The customer is present. Consent is captured and a mandate is created. | | Cards | Capitec VRP | | - | - | - | | **Customer interaction** | Yes, customer completes checkout flow | Yes, customer approves consent in Capitec Pay App | | **Provider-side registration** | Yes, for some card networks | Yes, VRP consent created at Capitec with explicit terms | | **Recurrence** | Optional, some card providers use it as a consent hint for interval and duration. Enforcement varies by network | Required, used to set the consent interval. Sourced from `mandate_options.recurrence` for `scheduled` or `mandate_options.terms.recurrence` for `installment` | | **Variable amount** | Yes | Yes, a variable amount can be collected, but `amount.max` sets the consent ceiling, fixed for the life of the consent | | **Amount and period constraints** | Managed by Moment | Yes, `amount.max` sets the consent ceiling and cannot be updated. Other constraints managed by Moment | Capitec VRP requires a consented cadence. It is eligible only on `scheduled` or `installment` mandates; `on_demand` mandates do not carry a cadence and are not supported for VRP. For `scheduled` mandates, set `recurrence` directly on `mandate_options`. For `installment` mandates, set `recurrence` on `terms` (both `periodic` and `fixed` term types support this). Selecting `capitec_vrp` on an `on_demand` mandate is rejected. Capitec Pay supports Variable Recurring Payments (VRP) for `scheduled` and `installment` mandates only. When creating a `first_in_series` session for Capitec Pay, `mandate_options` must include: * `type` set to `scheduled` or `installment`. * A `recurrence` set to one of the following supported combinations of `type` and `interval_count` (`mandate_options.recurrence` for `scheduled`, `mandate_options.terms.recurrence` for `installment`): | `type` | `interval_count` | | - | - | | `daily` | `1` | | `weekly` | `1` | | `weekly` | `2` | | `monthly` | `1` | | `monthly` | `6` | | `yearly` | `1` | Any other combination is not supported by Capitec Pay, therefore Capitec Pay will not be visible on during Checkout. *** ### `next_in_series` The customer is not present. Payment is executed against an existing mandate. | | Cards | Capitec VRP | | - | - | - | | **Customer interaction** | No | No | | **Mechanism** | Payment collected against saved card | Payment collected against stored consent | | **Variable amount** | Yes | Yes, a variable amount can be collected, but constrained within `amount.max` set at consent creation | | **Automated via subscription** | Yes | Yes | | **Pre-payment validation** | Mandate active | Mandate active and consent status verified with Capitec before each collection | #### Amount constraints The effective minimum and maximum for a `next_in_series` session depend on the session amount set at `first_in_series` and which of `amount.min` or `amount.max` were specified on [`mandate_options`](/api-reference/collect/payment_sessions/create#body-options-one-of-1-mandate-options). | Session amount at `first_in_series` | `amount.min` specified | `amount.max` specified | Effective minimum | Effective maximum | | - | - | - | - | - | | `0` | No | No | Payment method minimum | Payment method maximum | | `0` | Yes | No | Specified minimum | Payment method maximum | | `0` | No | Yes | Payment method minimum | Specified maximum | | `0` | Yes | Yes | Specified minimum | Specified maximum | | `> 0` | No | No | Payment method minimum | Session amount | | `> 0` | Yes | No | Specified minimum | Session amount | | `> 0` | No | Yes | Payment method minimum | Specified maximum | | `> 0` | Yes | Yes | Specified minimum | Specified maximum | # Use Cases Source: https://docs.momentco.io/documentation/collect/payment-sessions/mandates/use-cases Common scenarios for mandates A mandate is the merchant's standing permission to collect from a customer in the future. The scenarios below describe the most common shapes that permission takes, covering what the customer agrees to, how the platform behaves at collection time, and the configuration that expresses each one. The customer agrees to pay a fixed amount on a regular cadence, the way streaming services, gym memberships, and SaaS plans bill. The customer authorises once during sign-up; every subsequent month the merchant collects without involving them. The configuration locks down the price so the customer cannot be charged a different amount. ```json theme={"system"} { "mandate_options": { "type": "scheduled", "amount": 9900, "recurrence": { "type": "monthly", "interval_count": 1 } } } ``` By default (`adjustment: "nearest_weekday"`), if the chosen day falls on a Saturday or Sunday, the collection is moved to the closest weekday so banks process it on a working day. With `days: [1]`, a 1st that falls on Saturday is collected on the Friday before, and a 1st that falls on Sunday is collected on the Monday after. To collect on the 1st regardless of weekend, turn this behaviour off: ```json theme={"system"} { "mandate_options": { "type": "scheduled", "amount": 9900, "recurrence": { "type": "monthly", "interval_count": 1, "on": { "type": "day_of_month", "days": [1], "adjustment": "none" } } } } ``` The customer agrees to pay a predictable amount each month, typically aligned with payday or a billing cycle. ```json theme={"system"} { "mandate_options": { "type": "scheduled", "amount": 145000, "recurrence": { "type": "monthly", "interval_count": 1 } } } ``` The customer agrees to pay on the last working day of every month, regardless of how many days the month has. This is a common pattern for payroll-aligned obligations such as rent, levies, or loan repayments. Using the `last` keyword instead of a specific number means the collection always lands on the actual final day of the month, even in February. ```json theme={"system"} { "mandate_options": { "type": "scheduled", "amount": 250000, "recurrence": { "type": "monthly", "interval_count": 1, "on": { "type": "day_of_month", "days": ["last"], "adjustment": "previous_weekday" } } } } ``` Integer day values are capped at 28, since every month has at least 28 days and higher values would be ambiguous in shorter months. This means a fixed day like 28 runs every month, but it isn't the actual last day in 29, 30, or 31-day months. If the intent is "every month-end", use `last` instead, which always resolves to the true final day of the month regardless of its length. ```json theme={"system"} { "mandate_options": { "type": "scheduled", "amount": 250000, "recurrence": { "type": "monthly", "interval_count": 1, "on": { "type": "day_of_month", "days": [28], "adjustment": "previous_weekday" } } } } ``` The customer agrees to split a monthly obligation across two collection dates, common for rent, body-corporate levies, or any arrangement where one large debit would strain cashflow. ```json theme={"system"} { "mandate_options": { "type": "scheduled", "amount": 50000, "recurrence": { "type": "monthly", "interval_count": 1, "on": { "type": "day_of_month", "days": [1, 15], "adjustment": "nearest_weekday" } } } } ``` Each configured day must be distinct. The platform rejects the configuration if the same day appears twice, or if two entries describe the same date (for instance day 28 and `last` in a 28-day month like February): ```json theme={"system"} { "mandate_options": { "type": "scheduled", "amount": 50000, "recurrence": { "type": "monthly", "interval_count": 1, "on": { "type": "day_of_month", "days": [1, 15, 28, "last"] } } } } ``` The customer agrees to a collection on a specific weekday occurrence within each month, for example the second Tuesday (a common payday cadence in some markets) or the last Friday (typical for payroll-aligned subscriptions). ```json theme={"system"} { "mandate_options": { "type": "scheduled", "amount": 75000, "recurrence": { "type": "monthly", "interval_count": 1, "on": { "type": "nth_day_of_month", "day": "fri", "occurrence": "last" } } } } ``` A given weekday appears either 4 or 5 times in a calendar month. Asking for the 5th occurrence means there's no collection in months that only contain 4, which happens regularly, especially in February. If the intent is "always the last", use `"last"` instead of a number: ```json theme={"system"} { "mandate_options": { "type": "scheduled", "amount": 75000, "recurrence": { "type": "monthly", "interval_count": 1, "on": { "type": "nth_day_of_month", "day": "fri", "occurrence": 5 } } } } ``` The customer agrees to a single collection per year, such as annual subscriptions, professional association dues, or insurance premiums. ```json theme={"system"} { "mandate_options": { "type": "scheduled", "amount": 120000, "recurrence": { "type": "yearly", "interval_count": 1 } } } ``` February 29 is a valid date but only exists in leap years. Configuring an annual collection for Feb 29 means it runs in 2024 and 2028 but is skipped in 2025, 2026, and 2027. If the intent is "once a year around month-end February", target Feb 28 or use a month-end configuration instead: ```json theme={"system"} { "mandate_options": { "type": "scheduled", "amount": 120000, "recurrence": { "type": "yearly", "interval_count": 1, "on": { "type": "day_of_year", "dates": [{ "month": 2, "day": 29 }] } } } } ``` The customer agrees to one or more collections per week, such as weekly delivery services, lesson fees, or staff allowances. ```json theme={"system"} { "mandate_options": { "type": "scheduled", "amount": 20000, "recurrence": { "type": "weekly", "interval_count": 1 } } } ``` The customer agrees that the merchant may collect varying amounts on demand, up to an agreed ceiling, such as wallet auto top-ups, ride-hailing balances, or prepaid utility refills. There's no consented cadence; the merchant decides when to collect. ```json theme={"system"} { "mandate_options": { "type": "on_demand", "amount": { "max": 50000 }, "validity_period": { "start_date": "2026-01-01", "end_date": "2026-12-31" } } } ``` Payment methods whose consent surface requires a cadence (such as Capitec VRP) are not eligible on `on_demand`. See the next scenario for the VRP equivalent of this use case. Capitec VRP has no `on_demand` equivalent: the consent surface requires a cadence. A `scheduled` mandate with a daily `recurrence` approximates ad-hoc top-ups instead, consenting to collection on any day up to the ceiling. ```json theme={"system"} { "mandate_options": { "type": "scheduled", "amount": { "max": 50000 }, "recurrence": { "type": "daily", "interval_count": 1 }, "validity_period": { "start_date": "2026-01-01", "end_date": "2026-12-31" } } } ``` See [Recurring Payments](/documentation/collect/payment-sessions/recurring-payments/overview) for how mandate type and payment method eligibility fit together. The customer agrees to a billing rhythm (for example monthly) with the amount varying within an agreed range based on actual usage, so the customer is never surprised by a charge outside the expected ballpark. ```json theme={"system"} { "mandate_options": { "type": "scheduled", "amount": { "min": 1000, "max": 200000 }, "recurrence": { "type": "monthly", "interval_count": 1 } } } ``` The customer agrees to a fixed number of collections over a fixed time window, such as a 6-month donation pledge, a promotional payment plan, or a capped fundraising commitment. ```json theme={"system"} { "mandate_options": { "type": "scheduled", "amount": 10000, "max_occurrences": 6, "validity_period": { "start_date": "2026-01-01", "end_date": "2026-06-30" }, "recurrence": { "type": "monthly", "interval_count": 1 } } } ``` Once the agreed limit is reached (six successful collections in this case, or the validity period ending on June 30, 2026), the mandate is automatically marked as `EXPIRED` and any further collection attempt is rejected. The customer keeps control: they never pay more than they originally agreed to. See [Lifecycle](/documentation/collect/payment-sessions/mandates/lifecycle) for how this is reflected in the mandate's status. For configuration details on each field, see [Consent](/documentation/collect/payment-sessions/mandates/consent). # Overview Source: https://docs.momentco.io/documentation/collect/payment-sessions/overview Introduction to Payment Sessions **Payment Sessions** is the recommended integration for payment collection: rather than integrating separately with different payment methods and payment flows, merchants integrate once, and the session manages the underlying payment experience. Whether collecting a once-off payment, establishing a recurring payment, verifying a payment method, or completing a deferred capture, the integration remains consistent. ## Why Payment Sessions? Payment Sessions is designed to optimise for simplicity, consistency, and broad payment method support, not direct control over payment execution. Different payment methods often require different customer interactions, including redirects, QR codes, mobile app approvals, payment instructions, and asynchronous confirmation. The session manages these execution details behind a managed Checkout experience, allowing merchants to support many payment methods through a single, consistent integration without implementing payment-method-specific execution logic. This reduces implementation effort, lowers ongoing maintenance, and allows merchants to focus on building their customer experience rather than payment execution flows. Underneath every `Payment Session` is a `Payment`: the record of collecting money, which may involve more than one attempt before it succeeds or fails. The session handles this for you, managing the back-and-forth of redirects, approvals, and status checks so you don't have to. Today, you can [look up a payment's current status](/api-reference/collect/payments/retrieve) directly, and take action on a payment created through a session, such as [capturing](/api-reference/collect/payments/capture) or [voiding](/api-reference/collect/payments/void) a previously authorised payment. See [Authorisation and Capture](/documentation/collect/payment-sessions/authorisation-capture/overview) for the full flow. Refunding a captured payment is coming soon. ## Payment Flows Payment Sessions supports a wide range of payment collection scenarios through the same API. * **One-time payments**: Collect immediate customer payments, optionally saving the payment method for future customer-present use. * **Recurring payments**: Authorise a mandate once, then collect future payments yourself or automate collection with a subscription. Authorisation and capture, payment method verification, and payment allocation aren't separate flows; they're capabilities that apply on top of either one-time or recurring payments. * **Authorisation and capture**: Authorise funds now and capture or void them later, whether on a one-time or a recurring payment. * **Payment method verification**: Verify and save a payment method without charging the customer, ahead of a future one-time or recurring payment. * **Payment allocation**: Declare the purpose and target of a payment for compliance enforcement and automated settlement, on either flow. Each flow builds upon the same Payment Sessions model, enabling merchants to adopt additional capabilities without introducing new integration patterns. *** ## How It Works ```mermaid theme={"system"} sequenceDiagram participant Customer participant Server as Your Server participant API as Payment Sessions API participant Checkout Server->>API: Create Payment Session API-->>Server: Return session_url Server-->>Customer: Redirect to session_url Customer->>Checkout: Complete payment Checkout-->>API: Payment processed API-->>Server: Webhook: payment_session.completed ``` Start by creating a payment session with all necessary details like amount, currency, and payment type. For payments where the customer is present, redirect them to the checkout page provided in the `session_url` to complete the payment. For recurring payments where the customer is not present, use the customer's saved payment method to process the payment without any customer interaction. Monitor the status of your payments in real time using webhooks or API calls. *** ## Getting Started Choose the quickstart that matches your first integration: * **[One-time Payments Quickstart](/api-reference/collect/payment_sessions/quickstart-one-time)**: Accept immediate customer payments. * **[Recurring Payments Quickstart](/api-reference/collect/payment_sessions/quickstart-recurring)**: Support subscriptions and scheduled payments. *** ## Explore Further #### Core Concepts Go deeper on how a Payment Session behaves once created.
Compare the session types and what each one supports. See how Payment Sessions map to common business scenarios. Understand the states a payment session moves through from creation to completion. React to session events in real time.
#### Recurring Payments Authorise a customer once, then collect future payments yourself or automate them on a schedule.
How mandates, subscriptions, and payment methods work together to support recurring payments. Authorise future merchant-initiated payments on behalf of the customer. Automate recurring collections on a schedule.
#### Additional Flows Capture, verification, and allocation cut across both one-time and recurring payments rather than standing alone.
Reserve funds at checkout and capture or void them later. Verify and save a customer's payment method without charging them. Declare the purpose and target of a payment for compliance enforcement and automated settlement.
# Payment Allocation Source: https://docs.momentco.io/documentation/collect/payment-sessions/payment-allocation Declare the purpose and target of a payment for compliance enforcement and automated settlement. **Coming Soon.** The specification below is published for early review and may change before release. Many payments carry more meaning than the amount and the payer. A deposit into a gaming account must be screened for responsible gambling. A wallet top-up requires the recipient's identity for AML checks. A payment against a bill needs to be applied to that specific record on completion. Payment allocation captures this economic context in a structured, validated form, separate from payer identity and free-form metadata, so Moment can enforce the right compliance rules and automatically settle the right records. To set this context, include the `payment_allocation` field when creating a Payment Session. It is optional for most integrations. In regulated verticals such as gaming and wallet funding, it is required for compliance screening and regulatory reporting. Once a session is created, the allocation cannot be changed. ## How it works The `customer` and `payment_allocation` fields on a Payment Session serve distinct roles. `customer` identifies who is making the payment. `payment_allocation` identifies what the payment is for and which record it applies to. Two fields make up a payment allocation: * **`purpose`**: the reason the payment is being made, for example `wallet_funding` or `gaming_deposit`. * **`applies_to`**: the specific record the payment is applied against, identified by `type`. These fields are independent. The same purpose can apply to different target types, and the same target type can serve different purposes. Together they determine which fields are required and how the payment is validated. ## Examples A customer is loading funds into a wallet held in the merchant system or an external platform. Moment has no prior knowledge of the wallet holder, so identity and jurisdiction attributes are required for AML, PEP, and sanctions screening. ```json theme={"system"} { "amount": 50000, "currency": "ZAR", "payment_allocation": { "purpose": "wallet_funding", "applies_to": { "type": "merchant_wallet", "id": "wallet_customer_456", "attributes": { "recipient": { "name": "Jane Smith", "country": "ZA", "identification": { "type": "national_id", "number": "8001015009087" } }, "jurisdiction": "ZA" } } } } ``` A customer is loading funds into a wallet managed by Moment. Identity and KYC data was captured at wallet onboarding. No attributes are required. ```json theme={"system"} { "amount": 50000, "currency": "ZAR", "payment_allocation": { "purpose": "wallet_funding", "applies_to": { "type": "wallet", "id": "wlt_abc123" } } } ``` A customer is paying against a specific bill managed on the Moment platform. ```json theme={"system"} { "amount": 100000, "currency": "ZAR", "payment_allocation": { "purpose": "settlement", "applies_to": { "type": "bill", "id": "bill_abc123" } } } ``` A customer is depositing funds into a gaming account. Regulatory, identity, and responsible gambling attributes are required. ```json theme={"system"} { "amount": 20000, "currency": "ZAR", "payment_allocation": { "purpose": "gaming_deposit", "applies_to": { "type": "merchant_gaming_account", "id": "player_789", "attributes": { "recipient": { "name": "John Doe", "country": "ZA", "identification": { "type": "national_id", "number": "9001015009087" } }, "gaming": { "kyc_status": "verified", "player_status": "active", "deposit_limit_applied": true }, "jurisdiction": "ZA" } } } } ``` *** ## Payment Purpose The `purpose` field expresses the reason for the payment. It must be permitted by the merchant compliance profile. | Purpose | Description | | - | - | | `purchase` | Standard purchase or checkout payment. | | `wallet_funding` | Loading funds into a wallet. | | `gaming_deposit` | Deposit into a gaming or gambling account. | | `settlement` | Payment against a Moment-managed record: customer, account, bill, or payment request. | | `invoice_payment` | Payment against an invoice in the merchant system or an external platform. | | `merchant_defined` | Custom purpose defined by the merchant. | The following purposes are planned but not yet available: `account_top_up`, `loan_repayment`, `subscription_payment`, `airline_booking`, `lodging`, `healthcare`, `debt_repayment`, `cruise`. Contact us to discuss further. *** ## Payment Target A payment target is the specific record the payment is applied against: a wallet being funded, a bill being settled, or a gaming account receiving a deposit. The `applies_to` field sets up the payment target and enables automated application of the funds against that record on completion. ### Type The `type` field specifies the kind of target, for example a wallet, gaming account, or bill. It determines which fields are required, where `id` comes from, and which `attributes` apply. Targets fall into two categories: Moment-managed and merchant-managed. **Moment-managed targets.** Moment holds identity and compliance context for these objects. No compliance attributes are required at transaction time. | Type | `id` | Description | | - | - | - | | `wallet` | Moment wallet ID | A Moment-managed wallet. | | `customer` | Moment customer ID | A Moment-managed customer. Moment resolves which records to settle. | | `account` | Moment account ID | A Moment-managed account. Moment resolves which records to settle. | | `bill` | Moment bill ID | A Moment-managed bill. | | `payment_request` | Moment payment request ID | A Moment-managed payment request. | **Merchant-managed targets.** Moment has no prior knowledge of these objects. Identity and compliance context must be supplied in `attributes` where required. | Type | `id` | Description | | - | - | - | | `merchant_wallet` | Merchant-defined | A wallet in the merchant system or an external platform. | | `merchant_invoice` | Merchant-defined | An invoice in the merchant system or an external platform. | | `merchant_gaming_account` | Merchant-defined | A gaming or gambling account in the merchant system. | | `merchant_defined` | Merchant-defined | A custom target type defined by the merchant. | | `unstructured` | Not applicable | A descriptive-only target with no structured identifier. | ### Description An optional human-readable label for the target, available on all target types. For `unstructured` targets, at least one of `description` or `attributes` must be present. ```json theme={"system"} { "type": "unstructured", "description": "Annual membership renewal for account AC-00123" } ``` ### Attributes Compliance and identity data about the payment target is passed in `attributes`. Required fields depend on the target type. For `merchant_wallet` and `merchant_gaming_account` targets, `attributes` is required. Moment has no prior knowledge of the recipient, so identity and jurisdiction data is needed for compliance screening. Unknown fields are rejected. These targets also typically require the `customer` field to be set on the session, identifying the payer alongside the recipient. For all other target types, `attributes` is optional. The Required column reflects API-level defaults. Fields marked **Conditional** are optional by default but may be required at runtime by the merchant compliance profile, for example identity document for FICA, address for enhanced due diligence, or date of birth for age verification in licensed gaming jurisdictions. #### Wallet Funding Fields **`recipient`** (required) | Field | Description | Required | | - | - | - | | `name` | Full legal name of the recipient. | Yes | | `first_name` | First name. Optional complement to `name` for providers that require split name fields. | Conditional | | `middle_name` | Middle name. Used for identity disambiguation. | Conditional | | `last_name` | Last name. Optional complement to `name` for providers that require split name fields. | Conditional | | `country` | Country of residence of the recipient. ISO 3166-1 alpha-2. | Yes | **`recipient.identification`** (optional) | Field | Description | Required | | - | - | - | | `type` | Type of identity document. One of: `national_id`, `passport`, `drivers_license`, `asylum_seeker_permit`, `other`. | Conditional | | `number` | Identity document number. | Conditional | | `issuing_country` | Country that issued the identity document. May differ from `country` for foreign nationals. | Conditional | **`recipient.address`** (optional) | Field | Description | Required | | - | - | - | | `line1` | First line of the recipient's address. | Conditional | | `line2` | Second line of the recipient's address. | Conditional | | `city` | City. | Conditional | | `state` | State, province, or region. | Conditional | | `postal_code` | Postal code. | Conditional | **`jurisdiction`** (required) Regulatory jurisdiction of the wallet scheme. May differ from `recipient.country`. **`account`** (optional) | Field | Description | Required | | - | - | - | | `type` | Type of wallet account (for example `savings`, `current`, `ewallet`). | No | | `program` | Program or scheme identifier for the wallet. | No | #### Gaming Deposit Fields **`recipient`** (required) | Field | Description | Required | | - | - | - | | `name` | Full legal name of the player. | Yes | | `first_name` | First name. Optional complement to `name` for providers that require split name fields. | Conditional | | `middle_name` | Middle name. Used for identity disambiguation. | Conditional | | `last_name` | Last name. Optional complement to `name` for providers that require split name fields. | Conditional | | `country` | Country of residence of the player. ISO 3166-1 alpha-2. | Yes | | `date_of_birth` | Date of birth in `yyyy-mm-dd` format. Used for age verification in licensed gaming jurisdictions. | Conditional | **`recipient.identification`** (optional) | Field | Description | Required | | - | - | - | | `type` | Type of identity document. One of: `national_id`, `passport`, `drivers_license`, `asylum_seeker_permit`, `other`. | Conditional | | `number` | Identity document number. | Conditional | | `issuing_country` | Country that issued the identity document. May differ from `country` for foreign nationals. | Conditional | **`recipient.address`** (optional) | Field | Description | Required | | - | - | - | | `line1` | First line of the player's address. | Conditional | | `line2` | Second line of the player's address. | Conditional | | `city` | City. | Conditional | | `state` | State, province, or region. | Conditional | | `postal_code` | Postal code. | Conditional | **`gaming`** (required) | Field | Description | Required | | - | - | - | | `kyc_status` | KYC verification status. One of: `verified`, `pending`, `unverified`. | Yes | | `player_status` | Account status. One of: `active`, `suspended`, `self_excluded`. | Yes | | `deposit_limit_applied` | Whether deposit limits have been checked and applied. Boolean. | Yes | | `tier` | Player classification or VIP tier. | No | **`jurisdiction`** (required) Regulatory jurisdiction of the gaming operator (for example `ZA`, `CW`). May differ from `recipient.country`. **`account`** (optional) | Field | Description | Required | | - | - | - | | `type` | Type of account (for example `savings`, `current`, `ewallet`). | No | | `program` | Gaming program or casino identifier. | No | *** ## Validation Validation runs at session creation. The request is rejected if any of the following rules are violated. | Rule | Detail | | - | - | | `payment_allocation` may be required | Optional at the API level. Required for merchants whose compliance profile sets `requires_payment_allocation`. Omitting it on those profiles results in a rejected request. | | `purpose` must be permitted | The purpose must be in the merchant compliance profile's allowed purposes. | | `applies_to` required by purpose | Some purposes require `applies_to`. Others make it optional. | | `id` required for most types | Required for all target types except `unstructured`. | | `unstructured` requires description or attributes | At least one of `description` or `attributes` must be present for `type: unstructured` targets. | | Required attributes for `merchant_wallet` and `merchant_gaming_account` | `attributes` is required and all required attribute fields must be present. Unknown attribute keys are rejected. | # Payment Method Verification Source: https://docs.momentco.io/documentation/collect/payment-sessions/payment-method-verification Verify a customer's payment method without taking payment Payment method verification allows merchants to confirm that a payment method is valid and save it for future use without charging the customer. It is initiated by creating a session with `amount: 0`. Payment method verification currently applies to card payments only. The Moment checkout will present only card payment methods on `amount: 0` sessions. Two session types are supported depending on the intended future payment flow: * **`one_time`**: verifies and saves the payment method for future customer-initiated payments such as repeat purchases * **`first_in_series`**: verifies the payment method and creates a mandate for future merchant-initiated payments such as subscriptions and recurring charges This is commonly used for: * **Card on file setup** where the customer saves a payment method for future purchases without being charged immediately * **Free trials** where the first charge happens after the trial period ends * **Subscription sign-ups** with a deferred first billing cycle `capture_method` is not relevant for payment method verification. Any value supplied via `payment_method_options.card.capture_method` on an `amount: 0` session is ignored because the zero amount already signals verification intent. No capture or void step follows a successful verification. ## Card on File Use `one_time` with `amount: 0` to verify and save a payment method for future customer-initiated purchases. `amount: 0` on a `one_time` session implicitly saves the payment method, so `checkout_options.save_payment_method` does not need to be supplied. If supplied, only `always` is accepted; other values are rejected. ```json theme={"system"} POST /collect/payment_sessions { "amount": 0, "currency": "ZAR", "type": "one_time", "options": { "checkout_options": { "return_url": "https://merchant.example/return" } } } ``` No mandate is created. The payment method is saved and available for future customer-present payments. ``` 1. Customer creates an account on an e-commerce platform 2. Create one_time session with amount: 0 3. Payment method is verified and saved 4. Webhook: payment.succeeded (authorised_amount: 0) 5. Customer makes a purchase later β†’ new one_time session using the saved payment method ``` ## Recurring Setup Use `first_in_series` with `amount: 0` to verify the payment method and create a mandate in a single step. No initial payment is taken. `mandate_options.amount` is required in this case because the session-level `amount: 0` cannot serve as the consent ceiling for future `next_in_series` payments. For full details on configuring the mandate created at this step, see [Mandates](/documentation/collect/payment-sessions/mandates/overview). ```json theme={"system"} POST /collect/payment_sessions { "amount": 0, "currency": "ZAR", "type": "first_in_series", "options": { "customer": { "name": "Jane", "email": "jane@example.com" }, "checkout_options": { "return_url": "https://merchant.example/return" }, "mandate_options": { "type": "scheduled", "amount": 2500, "recurrence": { "type": "monthly", "interval_count": 1 } } } } ``` On completion, the mandate is created and available for future `next_in_series` payments. No capture or void is needed. ``` 1. Customer signs up for a 14-day free trial 2. Create first_in_series session with amount: 0 3. Payment method is verified, mandate created 4. Webhook: payment.succeeded (authorised_amount: 0) 5. After trial ends β†’ create next_in_series session to charge the first billing cycle ``` ## Session Outcome On successful verification, the Payment Session transitions to `status: completed` with `payment_outcome: payment_method_verified`. This is a terminal outcome that signals no payment was taken but the payment method was verified and vaulted. The deprecated `payment_status` field continues to be populated with `paid` for backwards compatibility. ## Payment Object on Success On successful verification, the payment object reflects zero amounts. The `intent` field is set to `payment_method_verification` to make the nature of the payment explicit: ```json theme={"system"} { "id": "pay_Vn8kLmQ2rTwX4y", "status": "succeeded", "intent": "payment_method_verification", "amount": 0, "currency": "ZAR", "authorised_amount": 0, "paid_amount": 0, "voided_amount": 0, "country": "ZA", "payment_method_details": { "type": "card", "card": { "type": "CREDIT", "scheme": "VISA", "bin": "411111", "last4": "1234" } } } ``` No capture or void is required after verification. ## Webhooks * **`payment.succeeded`**: fired when verification succeeds. `intent` is `payment_method_verification` and `authorised_amount` is `0`. See the [Payments API Overview](/api-reference/collect/payments/overview) for the full list of webhook events. # Overview Source: https://docs.momentco.io/documentation/collect/payment-sessions/recurring-payments/overview How mandates, subscriptions, and payment methods work together to support recurring payments A recurring payment is made up of a mandate (the customer's consent) and an optional subscription (automated collection on a schedule). ## Two ways to collect Every recurring payment is underpinned by a [mandate](/documentation/collect/payment-sessions/mandates/overview): the customer's consent, captured once during the first payment (`first_in_series`) via the mandate options (`mandate_options`). From there, there are two ways to collect against it: * **Merchant-controlled**: you decide when to collect. Create a `next_in_series` session yourself, whenever you choose, with no further customer interaction. Collection stays constrained by the mandate options (`mandate_options`) supplied at consent. * **Automated**: the platform decides when to collect, on a schedule you define. Add subscription options (`subscription_options`) alongside the mandate options (`mandate_options`) when creating the mandate. Collection is scheduled and executed automatically, still constrained by those same mandate options. The mandate is the consent ceiling in both cases; the subscription, when present, simply runs within it. See below for the full detail on each.
The consent a customer provides for a merchant to initiate future charges. Automated collection on a schedule.
This page covers the supported use cases, which combinations are valid, and which payment methods support each. ## Evolving from one-time to recurring payments Adding recurring payments is a small extension to an existing one-time payment (`type: one_time`) integration, not a new integration project. Checkout redirection and payment handling stay the same: only session creation changes, from `type: one_time` to `type: first_in_series` with `mandate_options`, optionally alongside `subscription_options` for the automated path. For the automated path, store the returned `subscription_id` for future management (status checks, cancellation). See below for reference on the similarity: ```mermaid theme={"system"} flowchart LR subgraph today["Today: One Time"] direction TB A1["1. Create the session"] --> A2["2. Redirect to Checkout"] --> A3["3. Receive result"] end subgraph manual["Merchant Controlled Recurring"] direction TB B1["1. Create the session\n(with mandate options)"] --> B2["2. Redirect to Checkout\n(unchanged)"] --> B3["3. Receive result (store payment id)"] end subgraph auto["Automated Recurring"] direction TB C1["1. Create the session\n(with mandate options\nand subscription options)"] --> C2["2. Redirect to Checkout\n(unchanged)"] --> C3["3. Receive result\n(store payment_id and subscription_id)"] end today ~~~ manual ~~~ auto ``` ## Choosing a use case Two questions narrow the choice: ```mermaid theme={"system"} flowchart TD A["Did the customer consent\nto a recurrence?"] -->|No| B["Setup on demand mandate"] A -->|Yes| C{"Automate collection?"} C -->|No: merchant triggers\neach charge| D["Setup scheduled or installment mandate"] C -->|Yes| E["Setup scheduled or installment mandate with subscription options"] ``` 1. **Did the customer consent to a recurrence?** A recurrence is a rhythm the customer agreed to at consent time (for example, monthly on the 1st). 2. **Should the platform automate collection?** Or does the merchant trigger each charge manually? The use cases below map the answers to a concrete setup: a mandate type (`mandate_options.type`), optionally combined with `subscription_options`. ## Use cases | Payment method | Recurrence consented? | Platform-automated? | Setup | | - | - | - | - | | Card | No | No | `on_demand` mandate | | Card | Yes | No | `scheduled` mandate | | Card | Yes | Yes | `scheduled` mandate + `subscription_options` | | Capitec VRP | Yes | No | `scheduled` mandate | | Capitec VRP | Yes | Yes | `scheduled` mandate + `subscription_options` | | Card or VRP | Yes (`terms`) | Optional | `installment` mandate | Subscriptions need a mandate with a consented cadence (`scheduled` or `installment`); `on_demand` has none, so `subscription_options` isn't available there. For configuration payloads, see [Mandates: Examples](/documentation/collect/payment-sessions/mandates/examples) and [Subscriptions: Examples](/documentation/collect/payment-sessions/subscriptions/examples). For payment method eligibility per mandate type and full per-method behaviour, see [Supported Payment Methods](/documentation/collect/payment-sessions/mandates/supported-payment-methods). # Session Types Source: https://docs.momentco.io/documentation/collect/payment-sessions/session-types Compare one-time and recurring payment session types and what each supports Payment Sessions has two top-level flows: one-time payments and recurring payments. ## One-time payments A `one_time` session is a payment where the customer is present and completes payment in real time. It can optionally save the payment method for future customer-present use. ## Recurring payments A recurring payment is split across two session types: one that establishes the mandate, and one for each subsequent charge against it. Collection can be merchant-controlled (you trigger each subsequent charge yourself) or automated by adding a subscription. See [Recurring Payments](/documentation/collect/payment-sessions/recurring-payments/overview) for how mandates, subscriptions, and payment methods work together. * **`first_in_series`**: The first payment in a series, where the customer is present and authorises their payment method for future merchant-initiated payments. This authorisation is called a mandate, and is typically used to set up recurring payments such as subscriptions or instalment plans. * **`next_in_series`**: A subsequent payment in a series, where the customer is not present. Fully programmatic, no customer interaction required. A mandate is an authorisation that allows a merchant to initiate future payments when the customer is not present in the flow. ## Comparing the Session Types The following table summarises the differences: | | **`one_time`** | **`first_in_series`** | **`next_in_series`** | | - | - | - | - | | **Primary Purpose** | One-time payment by the customer. | First payment to set up future automatic payments. | Next payment in a series. | | **Customer Interaction** | Required (customer is present). | Required (customer is present). | Not required (customer is absent). | | **Saving Payment Method** | Optional (for customer present use). | Mandatory (for customer absent use). | Not applicable (uses saved method). | | **[Mandates](/documentation/collect/payment-sessions/mandates/overview)** | No mandate created. | Mandate created for future payments. | Relies on an existing mandate. | | **[Subscriptions](/documentation/collect/payment-sessions/subscriptions/overview)** | Not applicable. | Optional. Provide `subscription_options` alongside `mandate_options` to automate recurring collections on a schedule. | Not applicable. | | **Payment Flow** | Customer completes via Checkout page. | Customer completes via Checkout page. | Payment processed programmatically. | | **Examples** | One-off purchase with the option to save for future purchases. | Subscription setup or instalment plan. | Monthly subscription payment or utility bill. | ### Cross-Cutting Flows Authorisation and capture, payment method verification, and payment allocation aren't tied to a specific session type; they layer on top of any of the three. | | **`one_time`** | **`first_in_series`** | **`next_in_series`** | | - | - | - | - | | **[Authorisation and Capture](/documentation/collect/payment-sessions/authorisation-capture/overview)** | Supported via `capture_method: manual` for card payments. | Supported via `capture_method: manual` for card payments. | Supported via `capture_method: manual` for card payments. | | **[Payment Method Verification](/documentation/collect/payment-sessions/payment-method-verification)** | Supported with `amount: 0`. Verifies and saves the payment method for future customer-initiated payments. | Supported with `amount: 0`. Verifies the payment method and creates a mandate for future merchant-initiated payments. | Not applicable. | | **[Payment Allocation](/documentation/collect/payment-sessions/payment-allocation)** | Supported via `payment_allocation` on the session. | Supported via `payment_allocation` on the session. | Supported via `payment_allocation` on the session. | # Examples Source: https://docs.momentco.io/documentation/collect/payment-sessions/subscriptions/examples Common subscription configurations The examples below show `mandate_options` and `subscription_options` configuration fragments for common billing patterns. They are passed as part of a `first_in_series` payment session request. See [Quickstart: Recurring Payments](/api-reference/collect/payment_sessions/quickstart-recurring) for the full end-to-end flow. *** ### Common Examples #### Daily billing A subscription collecting 5.00 ZAR every day throughout 2026: ```json theme={"system"} { "mandate_options": { "type": "scheduled", "amount": 500, "validity_period": { "start_date": "2026-01-01", "end_date": "2026-12-31" }, "recurrence": { "type": "daily", "interval_count": 1 } }, "subscription_options": {} } ``` #### Weekly billing A subscription collecting 10.00 ZAR every Monday throughout 2026: ```json theme={"system"} { "mandate_options": { "type": "scheduled", "amount": 1000, "validity_period": { "start_date": "2026-01-01", "end_date": "2026-12-31" }, "recurrence": { "type": "weekly", "interval_count": 1, "on": { "days": ["mon"] } } }, "subscription_options": {} } ``` #### Monthly billing A subscription collecting 20.00 ZAR on the 1st of each month throughout 2026: ```json theme={"system"} { "mandate_options": { "type": "scheduled", "amount": 2000, "validity_period": { "start_date": "2026-01-01", "end_date": "2026-12-31" }, "recurrence": { "type": "monthly", "interval_count": 1, "on": { "type": "day_of_month", "days": [1], "adjustment": "nearest_weekday" } } }, "subscription_options": {} } ``` #### Quarterly billing A subscription collecting 60.00 ZAR on the 1st of every third month throughout 2026: ```json theme={"system"} { "mandate_options": { "type": "scheduled", "amount": 6000, "validity_period": { "start_date": "2026-01-01", "end_date": "2026-12-31" }, "recurrence": { "type": "monthly", "interval_count": 3, "on": { "type": "day_of_month", "days": [1], "adjustment": "nearest_weekday" } } }, "subscription_options": {} } ``` #### Annual billing A subscription collecting 240.00 ZAR on January 1st each year, valid for three years: ```json theme={"system"} { "mandate_options": { "type": "scheduled", "amount": 24000, "validity_period": { "start_date": "2026-01-01", "end_date": "2028-12-31" }, "recurrence": { "type": "yearly", "interval_count": 1, "on": { "type": "day_of_year", "dates": [ { "month": 1, "day": 1 } ] } } }, "subscription_options": {} } ``` #### Installment (periodic) A subscription automating 4 equal monthly installments of 250.00 ZAR, totalling 1000.00 ZAR, collected at 9:00 AM on the 1st of each month: ```json theme={"system"} { "mandate_options": { "type": "installment", "total_amount": 100000, "terms": { "type": "periodic", "recurrence": { "type": "monthly", "interval_count": 1, "on": { "type": "day_of_month", "days": [1], "adjustment": "nearest_weekday" } }, "max_occurrences": 4, "amount": 25000 }, "validity_period": { "start_date": "2026-04-01", "end_date": "2026-07-31" }, "timezone": "Africa/Johannesburg" }, "subscription_options": { "active_period": { "start_date": "2026-04-01", "end_date": "2026-07-31" }, "amount": 25000, "scheduled_time": "09:00" } } ```
### Complete Example A subscription for monthly payments of 20.00 ZAR on the 1st and 15th of each month, active throughout 2026, with a nearest-weekday adjustment: ```json theme={"system"} { "mandate_options": { "type": "scheduled", "amount": 2000, "validity_period": { "start_date": "2026-01-01", "end_date": "2026-12-31" }, "max_occurrences": 24, "timezone": "Africa/Johannesburg", "recurrence": { "type": "monthly", "interval_count": 1, "on": { "type": "day_of_month", "days": [1, 15], "adjustment": "nearest_weekday" } }, "period_limits": { "period": "month", "max_count": 2, "max_amount": 4000 } }, "subscription_options": { "active_period": { "start_date": "2026-01-01", "end_date": "2026-12-31" }, "amount": 2000, "scheduled_time": "09:00" } } ``` # Overview Source: https://docs.momentco.io/documentation/collect/payment-sessions/subscriptions/overview Configuring automated payment schedules A **subscription** automates future payments within the boundaries of a mandate. When `subscription_options` is provided on a `first_in_series` session, the platform schedules and executes recurring collections automatically. The [mandate](/documentation/collect/payment-sessions/mandates/overview) is the consent ceiling; the subscription runs within it. Subscriptions are valid only on `scheduled` and `installment` mandates, which carry a consented cadence the subscription engine uses to schedule collections. They are not available on `on_demand` mandates: a subscription needs a cadence, and `on_demand` has none. For when to pick each shape, see [Recurring Payments](/documentation/collect/payment-sessions/recurring-payments/overview). Subscription automation for `installment` mandates with `terms.type: fixed` is coming soon. Today, each installment in a `fixed` plan must be initiated via `next_in_series` sessions on the due dates defined in `items`. Subscription support is available now for `periodic` terms. Providing `subscription_options` is optional. Omitting it creates the mandate but leaves scheduling to the merchant, who must initiate each `next_in_series` payment manually. An empty `subscription_options` is valid and creates a subscription with all properties inherited or defaulted from the mandate: `active_period` inherits from `mandate_options.validity_period`, `amount` infers from `mandate_options.amount` or the original session amount, and `scheduled_time` defaults to `00:00`. If the mandate has no `validity_period`, the subscription runs indefinitely. A subscription's `active_period` must fall within the mandate's `validity_period`. The platform will not execute a payment that would violate any mandate constraint, regardless of what the subscription schedules. ### Active Period Defines the date range within which the subscription is active. Payments are only scheduled within this window. A subscription active throughout 2026: ```json theme={"system"} { "subscription_options": { "active_period": { "start_date": "2026-01-01", "end_date": "2026-12-31" } } } ``` If `start_date` is omitted, the subscription starts immediately (or inherits from `mandate_options.validity_period.start_date`). If `end_date` is omitted, it runs indefinitely (or inherits from `mandate_options.validity_period.end_date`). The active period must fall within `mandate_options.validity_period`. The mandate remains the authorisation ceiling and cannot be exceeded.
### Amount The fixed amount in the smallest currency unit collected for each scheduled payment. Collecting 20.00 ZAR on each scheduled payment: ```json theme={"system"} { "subscription_options": { "amount": 2000 } } ``` If omitted, the amount is inferred in order from: `mandate_options.amount` (if a fixed amount), then the original session amount.
### Recurrence The payment cadence is defined in `mandate_options`, not in `subscription_options`. The recurrence is the customer's consented cadence; the subscription engine uses it to schedule collections. For `scheduled` mandates, recurrence is at `mandate_options.recurrence`. For `installment` mandates with `terms.type: periodic`, it is at `mandate_options.terms.recurrence`. See [Mandates: Recurrence](/documentation/collect/payment-sessions/mandates/consent#recurrence) for the full reference and examples.
### Scheduled Time The time of day at which scheduled collections are executed. Specified in 24-hour `HH:MM` format and evaluated in the timezone set by `mandate_options.timezone`. Defaults to `00:00` if not specified. Collections executed at 9:00 AM each scheduled day: ```json theme={"system"} { "subscription_options": { "scheduled_time": "09:00" } } ``` *** For full configuration examples, see [Subscription Examples](/documentation/collect/payment-sessions/subscriptions/examples). # Use Cases Source: https://docs.momentco.io/documentation/collect/payment-sessions/use-cases Common payment session scenarios There are multiple possible scenarios, each tailored to specific customer and merchant interactions. Below are some examples, grouped by one-time payments, recurring payments, and cross-cutting flows. ## One-time Payments * **Scenario**: A one-time payment where the **customer is present**, and the payment method is **not saved** for future use. * **Use Case**: Customers making a one-time purchase without any desire to save their payment method for future use. * **Session Type**: `one_time` * **Behavior**: * The customer completes the payment via the hosted Checkout page. * The payment method is not saved because the `save_payment_method` option is set to `never`. * **Example**: Buying a product or service without storing the card for future payments. * **Scenario**: A one-time payment where the **customer is present**, and the payment method is **only saved** for future payments where the **customer is present**. * **Use Case**: Customers who want to reuse their payment method for other purchases initiated by them in the future. * **Session Type**: `one_time` * **Behavior**: * The customer completes the payment via the hosted Checkout page. * The payment method is saved for use when the **customer is present** and the `save_payment_method` option is set to `always` or `prompt`. * **Example**: Purchasing a product and saving the card for future use during checkout. *At a glance:* | **Scenario** | **Session Type** | **Customer Interaction** | **Saving Payment Method** | **Examples** | | - | - | - | - | - | | One-off payment without saving | `one_time` | Required (customer-present) | No | Buying a product one time. | | One-off payment with saving | `one_time` | Required (customer-present) | Yes (for future **customer-present** use) | Purchasing and saving card for later. | ## Recurring Payments See [Recurring Payments](/documentation/collect/payment-sessions/recurring-payments/overview) for how mandates, subscriptions, and payment methods work together. * **Scenario**: The first payment in a series where the **customer is present**, and the payment method is **saved** with a **mandate for future payments**. * **Use Case**: Merchants setting up recurring payments for subscriptions or installment plans. * **Session Type**: `first_in_series` * **Behavior**: * The customer completes the initial payment via the hosted Checkout page. * A mandate is created authorising future payments that occur when the **customer is not present**. * **Example**: Signing up for a monthly subscription with an initial payment. * **Scenario**: A follow-up payment where the **customer is not present**, using a payment method saved in a previous `first_in_series` session. * **Use Case**: A merchant initiates a recurring payment based on an existing mandate. * **Session Type**: `next_in_series` * **Behavior**: * No customer interaction is required. * The payment is processed programmatically using the saved payment method and existing mandate. * **Example**: Processing a subscription payment each month. *At a glance:* | **Scenario** | **Session Type** | **Customer Interaction** | **Saving Payment Method** | **Examples** | | - | - | - | - | - | | Initiate first payment | `first_in_series` | Required (customer-present) | Yes (for future **customer-not-present** use) | Signing up for a subscription. | | Make subsequent payment | `next_in_series` | Not required (customer-not-present) | Uses previously saved payment method | Monthly subscription payment. | ## Cross-Cutting Flows Authorisation and capture, payment method verification, and payment method selection aren't tied to a specific session type; they layer on top of a one-time or recurring payment. * **Scenario**: A merchant wants to control which payment methods are available or preselected when the customer lands on Checkout. * **Use Case**: Merchants who present payment method options on their own page and want Moment Checkout to open with a specific method already selected. * **Session Type**: `one_time` or `first_in_series` (any session where the customer lands on a hosted Checkout page). * **Behavior**: * The merchant creates a session with `visible_payment_methods: ["card"]`. * The customer lands on the Checkout page with the card form displayed directly, since only one payment method was specified. * If multiple values are passed in `visible_payment_methods`, the Checkout page displays all listed methods without preselecting any. * **Example**: A merchant's product page shows a card icon; clicking it opens Checkout with the card form already active. See the full [Quickstart Guide](/api-reference/collect/payment_sessions/quickstart-control-payment-methods) for more details. * **Scenario**: A payment where funds are **reserved** at checkout but **not settled** until the merchant explicitly captures them. * **Use Case**: Merchants who need to verify stock, review for fraud, or wait for shipment before collecting funds. * **Session Type**: `one_time` * **Behavior**: * The merchant sets `payment_method_options.card.capture_method: manual` when creating the session. * The customer completes the payment flow as normal. On success, funds are **authorised** (held) but not settled. * The merchant then uses the Payments API to [capture](/api-reference/collect/payments/capture) or [void](/api-reference/collect/payments/void) the authorisation. * **Example**: Authorising R1 500.00 at checkout, then capturing R1 000.00 when 2 of 3 items ship and voiding the remaining R500.00. This option is card-specific. If the customer pays with a non-card method, the payment falls back to immediate settlement. See the full [Authorisation & Capture guide](/documentation/collect/payment-sessions/authorisation-capture/overview) for details. * **Scenario**: Verify a customer's card **without reserving any funds** when setting up a mandate for future recurring payments. * **Use Case**: Subscription businesses that need to confirm a card is valid before initiating future charges (e.g., free trial sign-up). * **Session Type**: `first_in_series` * **Behavior**: * The merchant creates a `first_in_series` session with `amount: 0`. * The customer completes the checkout flow. The card is verified via payment method verification. * A mandate is created for future merchant-initiated payments without any initial charge. * **Example**: A subscription service verifying a customer's card at sign-up before the first billing cycle begins. See the full [Authorisation & Capture guide](/documentation/collect/payment-sessions/authorisation-capture/overview#payment-method-verification) for details. * **Scenario**: Verify and save a customer's card **without charging them**, for future customer-initiated purchases. * **Use Case**: E-commerce platforms or marketplaces where customers save a card at account creation for faster future checkouts. * **Session Type**: `one_time` * **Behavior**: * The merchant creates a `one_time` session with `amount: 0`. * The customer completes the checkout flow. The card is verified and saved. * No mandate is created. The saved card is available for future customer-present payments. * **Example**: A customer adding a payment method to their account without making a purchase. See the full [Authorisation & Capture guide](/documentation/collect/payment-sessions/authorisation-capture/overview#payment-method-verification) for details. *At a glance:* | **Scenario** | **Session Type** | **Customer Interaction** | **Saving Payment Method** | **Examples** | | - | - | - | - | - | | Control visible payment methods in Checkout | `one_time` or `first_in_series` | Required (customer-present) | Optional | Displaying only card on the Checkout page. | | Authorise now, capture later | `one_time` | Required (customer-present) | Optional | Pre-order, ship-then-capture, fraud review. | | Verify card and create a mandate | `first_in_series` | Required (customer-present) | Yes (for future **customer-not-present** use) | Card verification at subscription sign-up. | | Verify and save a card | `one_time` | Required (customer-present) | Yes (for future **customer-present** use) | Saving a card without charging. | # Webhooks Source: https://docs.momentco.io/documentation/collect/payment-sessions/webhooks Tracking payment session state changes via webhooks The state of the payment session can be tracked by consuming webhook events. The event will contain a `payment_session` object that includes, amongst other fields, the `created_at`, `updated_at` and `payment_id` fields. Where applicable the `customer_id` and `payment_method_id` fields, that are needed for a subsequent (`next_in_series`) payment, is also included, and is sometimes referred to as a `token`. The payment session events are: * **`payment_session.created`**: Indicates a payment session was created successfully and can be used to initiate the payment process. See the [payment\_session.created](/api-reference/collect/payment_sessions/webhook/created) event for more information. * **`payment_session.updated`**: Indicates a payment session was updated successfully and can be used for tracking the status of the payment session. See the [payment\_session.updated](/api-reference/collect/payment_sessions/webhook/updated) event for more information. * **`payment_session.completed`**: Indicates a payment session has completed. See the [payment\_session.completed](/api-reference/collect/payment_sessions/webhook/completed) event for more information. ### Error Codes πŸ‘‰ [See Common Error Codes](/api-reference/getting-started/errors) # Lifecycle Source: https://docs.momentco.io/documentation/collect/redemptions/lifecycle Redemption statuses and state transitions A redemption progresses through a series of states that describe its lifecycle, from creation through to a final outcome. ```mermaid theme={"system"} stateDiagram-v2 direction LR [*] --> draft: POST /redemptions draft --> reserved: if provider supports reservations draft --> confirmed: provider redeems stored value reserved --> confirmed: on confirm confirmed --> [*] reserved --> failed: release draft --> failed: on error failed --> [*] ``` * **`draft`**: `draft`: the redemption has been recorded but has not yet reached a final outcome. * `confirmed`: the redemption completed successfully and the requested amount was redeemed from the provider's stored value. **The merchant is owed money.** `redeemed_amount` represents the amount redeemed. * `failed`: the redemption could not be completed. The provider rejected the redemption (voucher already used, expired, invalid PIN, insufficient balance, etc.) or an error may have occurred. **No money has moved.** Additional information may be available in `last_error`. * `reserved`: value has been reserved and is awaiting confirmation. **Today, all supported providers complete redemption immediately.**\ \ **As a result, redemptions transition directly from** `draft `**to** `confirmed `**or** `failed`.\ \ See [`Providers`](/documentation/collect/redemptions/providers) for additional details. ### How a redemption works The lifecycle is consistent across stored value types. The examples below use voucher redemption, which is the stored value type supported today.\ \ The client makes a single API request. Moment manages the interaction with the underlying provider, including redemption processing, error handling, and recovery where supported.\ \ **Immediate Redemption** For all currently supported providers, redemption is completed in a single request. ```mermaid theme={"system"} sequenceDiagram autonumber participant C as Client participant R as Redemption Service participant P as Provider C->>R: POST /collect/redemptions Note over R: record redemption as `draft`
emit `redemption.created` R->>P: redeem value P-->>R: response alt redemption accepted Note over R: state `draft` β†’ `confirmed`
emit `redemption.confirmed` else redemption rejected Note over R: state `draft` β†’ `failed`
emit `redemption.failed` end R-->>C: HTTP 201 ``` **Reserved Redemption** Some providers may support reserving value before final confirmation. ```mermaid theme={"system"} sequenceDiagram autonumber participant C as Client participant R as Redemption Service participant P as Provider C->>R: POST /collect/redemptions R->>P: reserve value P-->>R: reserved Note over R: state `draft` β†’ `reserved`
emit `redemption.reserved` R-->>C: HTTP 201 C->>R: POST /collect/redemptions/{id}/confirm R->>P: confirm redemption P-->>R: confirmed Note over R: state `reserved` β†’ `confirmed`
emit `redemption.confirmed` R-->>C: HTTP 200 ``` ### **Voucher Redemption Outcomes** Voucher redemption may result in one of three outcomes. 1. **Full Redemption.** The requested amount is redeemed successfully and no value remains. 2. **Partial Redemption.** The requested amount is redeemed successfully and value remains after redemption. The remaining value may be returned as a replacement (change) voucher or delivered through another provider-specific mechanism. See Providers for more details. 3. **Failed Redemption.** The redemption is rejected and no value is redeemed. Common causes include invalid credentials, insufficient available value, expired vouchers, or previously redeemed vouchers. ### **Guarantees** * **Duplicate Redemption Protection.** Repeated requests using the same `Idempotency-Key` return the original outcome without creating an additional redemption. Requests using the same key with different request bodies are rejected. * **Remaining Value Preservation.** Where a provider returns remaining value after a partial redemption, that value is captured on the redemption and returned in the API response where supported. * **Recovery from Ambiguous Provider Responses.** When a provider response times out, the service resolves the redemption to a final `confirmed` or `failed` state (never an indeterminate "maybe"), using provider-specific recovery endpoints where available. * **Full Audit History.** Every redemption attempt, provider reference, state transition, and error is recorded on the redemption and remains available for auditing and troubleshooting. * **Consistent Events.** Webhook events remain consistent with the redemption state, allowing downstream systems to process redemption outcomes reliably. # Overview Source: https://docs.momentco.io/documentation/collect/redemptions/overview Introduction to redeeming stored value Redemption allows merchants to redeem third-party stored value and apply it towards purchases, wallet top-ups, account balances, subscriptions, or other merchant-defined outcomes.\ \ Stored value represents prepaid value held by a third party. Vouchers are the first stored value type supported today, including providers such as 1Voucher, OTT Mobile, and Blu Voucher. See [Stored Value](/documentation/collect/stored-values/overview) to check an instrument's balance before redeeming it.\ \ The service provides a consistent API across providers, handling provider-specific integration requirements and redemption flows on your behalf. ### **How redemption works** 1. A customer provides their stored value details. 2. The merchant submits a redemption request to Moment. 3. Moment validates and processes the redemption with the underlying provider. 4. On success, the redeemed value can be applied to the merchant’s product, service, balance, or checkout. 5. Any remaining value is returned according to the provider’s redemption rules.
See how redemptions map to common business scenarios. Understand the states a redemption moves through from creation to completion. Compare the supported voucher providers and their capabilities. React to redemption events in real time.
### Key features * **Consistent integration.** Use one API for supported providers, while keeping control of how the redemption experience is presented to your customer. * **Works across multiple providers.** Redeem value from providers such as 1Voucher, OTT Mobile, and Blu Voucher through a single integration. * **Stay up to date automatically.** Supported redemption types and their requirements can be discovered dynamically without application updates. See [`GET /collect/redemptions/types`](/api-reference/collect/redemptions/get-types), for more details. * **Track every redemption.** Monitor redemption status and outcomes in real time through the dashboard and webhooks. # Providers Source: https://docs.momentco.io/documentation/collect/redemptions/providers Supported stored value providers and their capabilities Providers may differ in supported capabilities, field requirements, and redemption behaviour. The supported providers can be discovered programmatically using the [redemption types](/api-reference/collect/redemptions/get-types) endpoint. The endpoint returns each redemption `type`, its capabilities, display metadata, and required `source` attributes, allowing clients to dynamically present available providers and collect the information required to redeem them. Use the discovery endpoint as the source of truth to automatically stay up to date as providers and capabilities evolve. ## Voucher Vouchers are redeemed through three providers today: **1Voucher**, **OTT Mobile**, and **Blu Telecom (Blu Voucher)**. Provider quick-reference | **Provider** | Type | Identifier | **Partial Redemption** | **Balance Lookup** | **Change Voucher Returned** | **SMS Delivery** | | :- | - | - | :- | :- | :- | :- | | 1Voucher | `vouchers.one_voucher` | `pin` (16 digit) | Yes | No | Yes, `change_voucher` field is returned in API Response | Yes, delivered by provider when `phone_number` is supplied | | Blu Voucher | `vouchers.blu` | `pin`
`phone_number` | Yes | Yes | Yes, `change_voucher` field is returned in API Response | No | | OTT Mobile | `vouchers.ott` | `token`
`serial_number` (for lookup) | Yes | Yes | No | Yes, delivered by provider when `phone_number` is supplied | **1Voucher** **Redemption Behaviour** Supports: * Full redemption * Partial redemption **Remaining Value** * Where value remains after redemption, a replacement voucher is returned through the `change_voucher` field (PIN, serial number, barcode, instructions). * Where value remains after redemption, a replacement voucher is delivered directly to the customer by SMS using the supplied phone number. The replacement PIN is the customer's only way to spend the residual balance, so it must be preserved and surfaced to the customer. \ **Blu Voucher** **Redemption Behaviour** Supports: * Full redemption * Partial redemption **Remaining Value** * Where value remains after redemption, a replacement voucher is returned through the `change_voucher` field. The replacement PIN is the customer's only way to spend the residual balance, so it must be preserved and surfaced to the customer. **OTT Mobile** **Redemption Behaviour** Supports: * Full redemption * Partial redemption **Remaining Value** * Where value remains after redemption, a replacement voucher is delivered directly to the customer by SMS using the supplied phone number. The replacement token is the customer's only way to spend the residual balance, so it must be preserved and surfaced to the customer. ## Gift Cards **Coming soon.** Gift cards will be supported as a stored value type in future. ### Loyalty Points **Coming soon.** Loyalty points will be supported as a stored value type in future. # Use Cases Source: https://docs.momentco.io/documentation/collect/redemptions/use-cases Common stored value redemption scenarios There are multiple redemption scenarios, each tailored to specific customer and merchant interactions. Below are some examples: * **Scenario**: Redemption for the full value of the redemption source. * **Use Case**: A customer with a 3rd Party store of value redeeming the full amount with the Merchant. * **Behaviour**: * The customer selects the type (the provider of the 3rd Party store of value) and provides the reference number (serial number, PIN, etc.). * Omit `amount` and `currency` on create. The full value of the voucher is redeemed. * **Example**: Topping up a gaming or trading wallet with a voucher. * **Scenario**: Redemption using partial value of the redemption source. * **Use Case**: A customer with a 3rd Party store of value redeeming a specific amount with the Merchant. * **Behaviour**: * The customer selects the type, sets the amount, and provides the reference number. * Depending on the type and provider, the customer may be required to provide additional information to manage any remaining value. * **Example**: Paying for airtime. # Webhooks Source: https://docs.momentco.io/documentation/collect/redemptions/webhooks Tracking redemption state changes via webhooks The state of a redemption can be tracked by consuming webhook events. Every state change emits an event carrying a `redemption` object that includes, amongst other fields, the `status`, `redeemed_amount`, `created_at`, and `updated_at` fields. On a partial settlement the `change_voucher` field is also included. The redemption events are: * **`redemption.created`**: Fired when a new redemption is created (status `draft`). See the [redemption.created](/api-reference/collect/redemptions/webhook/created) event. * **`redemption.confirmed`**: Fired when the redemption is successfully confirmed from the provider and the funds are consumed. See the [redemption.confirmed](/api-reference/collect/redemptions/webhook/confirmed) event. * **`redemption.failed`**: Fired whenever a state transition lands in a failed outcome. See the [redemption.failed](/api-reference/collect/redemptions/webhook/failed) event. ### Error Codes πŸ‘‰ [See Common Error Codes](/api-reference/getting-started/errors) # Overview Source: https://docs.momentco.io/documentation/collect/stored-values/overview Check stored value balance and metadata before redeeming Stored value represents prepaid value held by a third party, such as a voucher. A lookup checks an instrument's balance and metadata, without creating a redemption. Use it to validate an instrument or show a customer their balance before redeeming it. See [Redemptions](/documentation/collect/redemptions/overview) to redeem stored value. Query a provider for the current balance and metadata of a stored value instrument. ### Stored value types * **Voucher**: supported today, through OTT Mobile, and Blu Voucher. * **Gift Cards**: coming soon. ### Key points * **Stateless.** A lookup is a read that does not create or change a redemption. * **Provider scoped.** Pass the `type` and the `source` credentials the provider requires, the same shape used to create a redemption. List the supported types with [`GET /collect/redemptions/types`](/api-reference/collect/redemptions/get-types). # Use Cases Source: https://docs.momentco.io/documentation/collect/stored-values/use-cases Common stored value lookup scenarios Stored value lookups support a few common scenarios. Below are some examples: * **Scenario**: Confirm a stored value instrument's balance before redeeming it. * **Use Case**: A customer presents a stored value instrument, such as a voucher, and the Merchant wants to show its available balance, or verify it is valid, before starting a redemption. * **Behaviour**: * Choose the stored value `type` and provide the `source` credentials it requires. List the supported types with [`GET /collect/redemptions/types`](/api-reference/collect/redemptions/get-types). * Call [`POST /stored_value/lookup`](/api-reference/collect/stored-values/lookup) to fetch the balance, currency, and provider metadata. * If the balance is sufficient, create a redemption for the desired amount. * **Example**: Showing a voucher balance in a wallet top-up flow before the customer commits. # Capitec VRP Source: https://docs.momentco.io/documentation/network/capitec-vrp ## What is Capitec VRP? Capitec Variable Recurring Payments is like putting your bank account on file instead of your bank credit or debit card. It is an account-to-account collection mechanism that allows merchants to pull variable amounts from a customer's Capitec account on a recurring basis, against a mandate that the customer authorises directly inside the Capitec banking app. Think of it as a modernised alternative to card-on-file β€” purpose-built for recurring and subscription billing β€” with a simpler mandate flow, lower cost, and digital bank-level authentication resulting in strong customer authentication. It allows customers to make subscription and recurring payments directly from their bank accounts, providing a secure alternative to using credit cards for services like streaming, mobile contracts, grocery delivery, and more. ## How it compares to other recurring payment mechanisms | | Capitec VRP | Cards | | - | - | - | | **Mandate authorisation** | Customer consents in Capitec app | Customer consents to PSP | | **Dispute posture** | Burden of proof on customer given strong consent | Burden of proof on merchant to defend chargebacks | | **Collection retry** | Supported with merchant-initiated retry | Supported with merchant-initiated retry | | **Overall Cost of Collection** | Lower | Low-Mid | ### Capitec VRP vs. Cards For merchants currently using cards for subscription billing, Capitec VRP eliminates card expiry as a source of involuntary churn, removes card-not-present decline risk, and avoids card scheme fees. Customers may also be more confident agreeing to recurring payments with VRP due to their ability to cancel at any time in the app. The trade-off is reach: this instrument covers Capitec account holders only. For full market coverage, pair Capitec VRP with card acceptance or other bank rails. ## How it works **Step 1 β€” Mandate setup (one-time per customer)** Your customer approves the first-time payment and the mandate for subsequent recurring payments directly inside the Capitec banking app. The mandate defines the collection parameters: the merchant, the maximum amount, and the billing frequency. On approval, you can collect the first payment if due. **Step 2 β€” Recurring collection** On each billing date, you initiate collection via our API, or schedule it in advance with Moment Subscriptions. Funds are pulled from the customer's Capitec account against the active mandate. You receive a real-time confirmation or failure status, and settlement follows Moment's standard schedule. **Step 3 β€” Failed collections and retries** If a collection fails β€” insufficient funds, for example β€” you can retry the mandate. **Step 4 β€” Mandate revocation** Customers can revoke their mandate at any time directly inside the Capitec app. You will receive an [error](https://docs.momentco.io/api-reference/getting-started/errors#mandate-error-codes) when you attempt to collect against the revoked mandate. ## Ideal use cases Capitec VRP is well-suited for: * **Subscription billing with variable amounts** β€” insurance premiums, usage-based pricing, or any model where the amount changes month-to-month (e-commerce subscription boxes and regular goods deliveries, pay-as-you-go and on-demand digital platforms, and digital subscriptions (streaming, SaaS, usage-based services). See [supported recurring intervals](https://docs.momentco.io/documentation/collect/payment-sessions/mandates/supported-payment-methods#first_in_series). * **Utility and telco collections** β€” recurring obligation billing where you're already running debit order or DebiCheck flows and want a lower-cost, lower-friction alternative for Capitec customers * **Repayments and instalment plans** β€” where instalment amounts may change over the product term ## What to know before you go live **Capitec customers only.** This instrument covers customers who hold a Capitec bank account. For full South African market coverage, pair Capitec VRP with DebiCheck, EFT, or card acceptance for customers on other banks. Any customer using Capitec Pay is automatically enrolled for Capitec VRP. **Dispute process.** Because the mandate is bank-authenticated, disputes place the burden of proof on the customer β€” consistent with DebiCheck. Document your mandate confirmation flows carefully; this is your evidence in the event of a dispute. ## Getting started Capitec VRP is available to merchants on Moment's Payment Session API. To enable it on your account, contact your Moment account manager or reach out to our [support](/documentation/support) team. If you're evaluating Moment for recurring billing, [book a 20-minute walkthrough](mailto:gtm-engineering@momentco.net) to see the flow end to end. β†’ **Developer reference:** [Quickstart: Recurring Payments](/api-reference/collect/payment_sessions/quickstart-recurring) # In-Person Network Source: https://docs.momentco.io/documentation/network/in-person-network ## Introduction The in‑person payment network enables merchants to leverage our reach and collect payments at many different pay points, from large retailers to agents operating at small shops. Our goal is to rapidly increase merchants’ payment network distribution and truly allow customers to pay any ***place*** and any ***time*** they choose. ### Prevalence Consumer preferences are shifting to digital payment methods; however, many of the largest businesses collect more than 60% of payments using the in‑person network. These preferences do not only stem from a customer’s access (or lack of access) to digital payment methodsβ€”although that is sometimes the caseβ€”but also from strong emotional preferences. Trust in financial institutions is weak, and not all customers are comfortable enabling payments using cards or from their bank accounts. The in‑person network puts the customer in control. ## Pay Points Moment’s in‑person network is ubiquitous across South Africa, enabling consumers to pay at more than 300,000 locations. Consumers can make a payment at any of the following pay points: Group 261 ## Enablement Our in-person payment network is powered by our [Billing](https://docs.momentco.io/documentation/billing/overview) ecosystem. Once integrated with the Billing service, customers will be able to check the amount they owe anywhere on our network, and after they have paid, merchants receive a real‑time notification. ## Payer journey 1. The customer arrives at an in‑person payment location and requests to pay a bill. 2. The clerk (generic term for the POS teller, trader, or agent, depending on the market and payment‑location context) requests the customer’s unique identifier to fetch their payment details. 3. The customer provides their unique identifier for the merchant. 4. The clerk verifies the customer’s name and amount due, and may optionally ask the customer how much they want to pay and how they want to pay. 1. Payment locations and merchants can set minimum and maximum payment-amount rules. 2. Some locations can also share other bill details with the customer (for instance, the service or product the bill is for). 5. The customer successfully completes payment and the payment location notifies Moment that a payment has been completed. 6. Moment will notify the merchant in real-time that the customer has paid. 7. The customer will be provided a payment receipt which can be used as proof of payment in case of any support queries. # Overview Source: https://docs.momentco.io/documentation/network/overview Introduction to the Moment Network The **Moment Network** gives merchants access to a broad, multi-channel payment infrastructure across African markets. From in-person cash collection at over 300,000 pay points to digital payment methods and real-time bank transfers, the network is designed to meet customers where they are.
Enable customers to pay at retail and agent locations across South Africa. Explore all supported payment methods across African markets. Let customers start a payment digitally and complete it in person using a unique paycode. Real-time interbank payments via the PayShap network.
# Paycode Source: https://docs.momentco.io/documentation/network/paycode ## Use Paycode to tap into the in-person network with a single integration A **paycode** is a unique 14-digit 'card-like' identifier which allows a customer transacting in [Checkout](/documentation/collect/checkout) to start a payment journey on a digital platform and complete the payment at an in-person location on Moment's network. Merchants can access Moment's network using our Billing services, however this dual-integration approach may not be fit-for-purpose for every merchant. For merchants already interacting with their customers on a digitial platform (e-commerce site, mobile app, social media), paycode enables merchants to offer their customer's more ways to pay. ## How Paycode works 1. The payer selects an in-person brand in the Moment Checkout. 2. The payer is shown unique payment instructions, including the paycode, and directed to pay on our network. 3. The merchant receives a real-time notification from Moment when the payment is completed. ### Integration Paycode works with your existing integration--you can enable paycode in Checkout using any of our Collect products. ## Common Use Cases | **Use Case** | **Description** | | :- | :- | | E‑commerce checkouts | Let customers who don’t want to use cards/bank accounts complete online orders in‑person. | | Mobile apps (billers, wallets, super‑apps) | Apps initiate payment in‑app and finish at a retail/agent point on the Moment network. | | Subscription & invoice billing | Embed Checkout with paycode enabled in invoices so customers can settle bills at in‑person locations while keeping all billing in one system. | | Collections & arrears | Collections teams send Payment Requests with paycode enabled via SMS/email/WhatsApp so payers can clear arrears in‑person without sharing banking details. | | Utilities & telcos | Customers start top‑ups or bill payments digitally, receive a Payment Request with paycode enabled, and pay cash or card at any supported retailer/agent. | | Insurance premiums | Insurers issue Payment Requests with paycode enabled for monthly or ad‑hoc premiums, giving cash‑preferring customers a way to pay in‑person with real‑time confirmation. | | High‑risk or low‑trust segments | Merchants serving customers wary of sharing card or bank details use Checkout with paycode enabled to provide a β€œcash‑friendly” option with strong audit trails. | | Cross‑channel promotions | Campaigns that start online (ads, emails, QR codes) issue Payment Pages with paycodes enabled so redemptions and payments can happen at physical stores. | | Agent & field sales networks | Field agents place orders on behalf of customers and share a Payment Request with paycode enabled the customer to pay at any network location instead of handling cash directly. | | Social commerce & D2C sellers | Sellers taking orders via social media or chat tools send a Payment Request with paycode enabled as a secure, trackable way to pay in‑person. | | Call centres / assisted sales | Agents generate a Payment Reqeuest with paycode enabled during a call so the customer can complete payment later at an in‑person location, with automatic reconciliation. | | Partial or staged payments | Allow customers to initiate a larger purchase or lay‑by digitally and then make one or more in‑person payments using Checkout with paycode enabled. | # Payment Methods Source: https://docs.momentco.io/documentation/network/payment-methods Explore supported payment methods across African markets