# 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:
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:
## 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:
### 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"
}
```
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.
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.
***
## 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:
## 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
# PayShap
Source: https://docs.momentco.io/documentation/network/payshap
This is an introduction to PayShap
### **Introduction**
**Overview:**\
PayShap is a real-time payment service launched by BankservAfrica as part of South Africaβs Rapid Payments Programme (RPP). It enables instant, secure, and low-cost account-to-account payments, ideal for high-volume, low-value transactions. PayShap ensures that a person-to-merchant (P2M) payment moves seamlessly with instant notifications.
**Key Features:**
* **Real-Time Transactions**: Instant confirmation and fund transfer
* **Account-to-Account Transfers**: No middleman, just direct payments
* **Broad Bank Integration**: Supported by all major South African banks (except Tyme)
### **Business Use Cases**
**E-commerce**\
Merchants can offer PayShap as a payment option on their online stores, ensuring customers enjoy a seamless checkout experience for everyday purchases.
**Subscriptions & Recurring Billing**\
Great for services requiring recurring low-value payments, such as media, education, and SaaS platforms.
**In-Store Payments**\
Retailers can offer PayShap as a mode of payment to consumer as in-store payment option.
### **Payment Flow**
Here is a step-by-step view of a typical PayShap payment from a userβs perspective:
1. **Payment Selection**: Customer selects PayShap on their bank app.
2. **Enter ShapId**: The user enters the recipientβs (business or merchant) ShapId (e.g., `merchant-xyz@bank`).
3. **Payment Authorization**: The user approves the payment within their banking app.
4. **Instant Notification**: Confirmation is delivered to both payer and merchant in real-time.
### **Payment Lifecycle**
1. **Proxy Resolution**\
The payerβs bank resolves the ShapId to determine the recipientβs bank account and identity.
2. **Payment Authorization**\
User authorizes the payment.
Optionally, merchants can configure business logic via Moment or APIs to approve/reject transactions.
3. **Completion**\
Funds are instantly transferred and both parties receive confirmation. The transaction status updates to either "Closed" (success) or "Rejected" (failure).
### **Key Components of a PayShap Payment**
* **ShapId**\
A unique proxy ID (e.g., phone number, business ID) linked to a bank account.
* **Proxy & Domain**\
Structured as `proxy@domain` (e.g., `dstv-9818009123@nedbank`) for easy routing.
### **How to Enable PayShap on Your Platform**
Merchants can enable PayShap through any of the following integration modes:
1. **API adapter Integration**\
Through Momentβs API adapter, ideal for systems ready to integrate our endpoints with pre-defined request and response body structures that would be shared by Moment.
2. **Offline File Sharing**\
For lower-tech environments, Moment can accept batch files to process payments offline.
### **Conclusion**
PayShap is a transformative payment method for South African merchants and consumers. With real-time processing, mobile-first usability, and cost-effective infrastructure, it unlocks frictionless digital commerce. Whether youβre a startup, a retailer, or a billing platform, PayShap is a future-proof way to accept fast, safe payments across South Africa.
For more technical details, contact our support team to get started.
# Overview
Source: https://docs.momentco.io/documentation/overview
Everything you need to start building with Moment
Moment is a payments platform built for African markets. Whether you are accepting one-time payments, managing recurring billing, or distributing collections through an in-person network, the APIs and tools here give you everything you need to integrate, go live, and scale.
***
## Product Areas
Accept payments via Payment Sessions, Payment Links, and Checkout.
Manage customers, accounts, and bills in the Moment ecosystem.
Reach customers through Moment's in-person payment network across Africa.
Reconcile transactions and access settlement reports.
***
## Developers
Full reference documentation for all Moment APIs, authentication, error handling, and more.
Client libraries for JavaScript, React, React Native, Flutter, and Swift.
***
## Resources
Step-by-step walkthroughs for the Moment client portal and checkout flows.
Get help from the Moment team.
# Overview
Source: https://docs.momentco.io/documentation/reconciliation-reporting/overview
Introduction to Reconciliation and Reporting
Moment provides daily reconciliation and settlement reports delivered via SFTP, giving you visibility into the current state of transactions on the platform β processed, cleared, settled β and the funds flowing to your account.
Understand transaction states and how Moment reports on what has been processed, cleared, and settled.
Daily SFTP reports covering processed transactions and settlement activity, with full file format references.
# Reconciliation
Source: https://docs.momentco.io/documentation/reconciliation-reporting/reconciliation
How to Reconcile Transactios
## How to reconcile transactions
Reconciliation is the process of verifying transactions to confirm that your internal records match your bank and Moment records. This ensures that account balances are accurate between your records and bank accounts at the end of a given period.
Each transaction is billed, invoiced, and settled based on predefined agreements. Moment provides reconciliation and settlement reports to help you verify that funds are processed and settled as expected.
## Why is Reconciliation Important?
Proper reconciliation protects your business and ensures financial accuracy:
* **Detect discrepancies early** β Identify missing, duplicate, or mismatched transactions before they become larger issues
* **Maintain accurate financial records** β Ensure your accounting systems reflect actual payment activity
* **Verify settlement amounts** β Confirm that funds deposited to your bank account match expected net settlement amounts
* **Support compliance and auditing** β Provide clear audit trails for regulatory requirements and financial reviews
* **Enable informed decision-making** β Trust your financial data when making business decisions
The reconciliation workflow involves the exchange, processing, and reporting of reconciliation files between Moment and its merchants.
```mermaid theme={"system"}
%%{init: {'sequence': {'mirrorActors': false, 'messageAlign': 'center'}}}%%
sequenceDiagram
participant Moment
participant Merchant
Moment->>Merchant: T+1
Note over Moment,Merchant: Upon reconciliation, Moment uploads reconciliation file to Merchant SFTP
Moment->>Merchant: T+2
Note over Moment,Merchant: Moment uploads a Merchant Settlement Report to Merchant SFTP
```
# Reconciliation Report
Source: https://docs.momentco.io/documentation/reconciliation-reporting/reconciliation-report
Merchant Reconciliation Report file format and structure
The **Merchant Reconciliation Report** lists all transactions for a merchant that were processed and closed during a given period (typically 24 hours).
## File Name
```
____.csv
```
* **FILE\_GENERATION\_DATE**: The date the file is generated (YYYYMMDD format, e.g., 20241225).
* **UNIQUE\_FILE\_IDENTIFIER**: A fixed-value string identifying the system that produced the file.
* **PERIOD\_END\_DATE**: The end date of the reporting period (YYYYMMDD format, e.g., 20241225).
**Example:**
```
20241231_Moment_Recon_ae5811c4-0fb3-47de-8a9e-f8b298b9525b_20241230.csv
```
## File Header
| **Element** | **Value** | **Format** | **Notes** |
| - | - | - | - |
| Report Title | Moment Merchant Reconciliation Report | \[A-Z] | Fixed string. |
| Version | Report Version Number | \[-\~]; length: 20 | Matches the file format version. |
| Merchant | Merchant Alias | \[-\~]; length: 38 | Unique identifier for the merchant. |
| From | Start of the reporting period | | Transactions from this date/time are included. |
| Start Date | Period start date | | Start of the reporting window. |
| To | End of the reporting period | | Transactions up to this date/time are included. |
| End Date | Period end date | | End of the reporting window. |
## File Body
| **Column Name** | **Field Name** | **Description** | **Value Type/ Format** | **Example Value** |
| - | - | - | - | - |
| **Transaction Type** | transaction\_type | The type of the transaction. | string | Payment, Refund |
| **Transaction Timestamp** | transaction\_timestamp | The date and time when the transaction occurred. | datetime | 2024-01-31T14:30:00Z |
| **Moment Transaction ID** | reconciliation\_id | ID of the transaction on the Moment platform. Use this as a reference when contacting our support team. | string | 673bbe2805368010f36e9bee |
| **Merchant Reference ID** | merchant\_reference\_id | The unique ID referencing the merchant used when creating the transaction on the Moment platform. | string | 672bbe2805368010f36e123e |
| **Payment Method** | payment\_method | Name of the payment method type used when creating the transaction. | string | Capitec Pay |
| **Tender Type** | payment\_method\_tender | Name of the payment method tender used when creating the transaction. | string | Bank Transfer |
| **Customer Reference** | customer\_reference | The unique ID referencing the merchant used when creating the transaction on the Moment platform. | string | 123456789X1 |
| **Country** | country | The country where the funds of the transaction were collected from. | ISO 3166 alpha-2 | ZA |
| **Presentment Currency** | presentment\_currency | The currency of the transaction collected from the payer. | 3-letter ISO 4217 code | ZAR |
| **Presentment Amount** | presentment\_amount | The amount collected from the payer, in presentment currency. | decimal | 27.12 |
| **Settlement Currency** | settlement\_currency | The currency of the payment that will be settled to the Moment merchant. | 3-letter ISO 4217 code | ZAR |
| **Gross Settlement Amount** | gross\_settlement\_amount | The amount of the payment that will be settled to the merchant, in settlement currency. | decimal | 120 |
| **MDR %** | mdr\_percentage | MDR Percentage. | decimal | 10 |
| **MDR** | mdr | Merchant Discount Rate charged to a merchant for the payment processing. | decimal | 12 |
| **Fixed Fee** | fixed\_fee | Fixed fee charged. | decimal | 3 |
| **FX Fee** | fx\_fee | FX Fee. | decimal | 0 |
| **Total Fee** | total\_fee | The total fee charged. | decimal | 15 |
| **Fee Currency** | fee\_currency | The currency of the fee. | 3-letter ISO 4217 code | ZAR |
| **VAT Amount** | vat\_amount | VAT of the total fee amount charged. | decimal | 2.25 |
| **Net Settlement Amount** | net\_settlement\_amount | Net settlement amount that will be settled to Moment merchant. | decimal | 102.75 |
# Overview
Source: https://docs.momentco.io/documentation/reconciliation-reporting/reports
Merchant Reconciliation and Settlement Report formats
Moment generates two types of financial reports, delivered to your SFTP on a daily basis. Use them to track the state of transactions processed on the platform and confirm the amounts being settled to your bank account.
Each report covers a different lens: the Reconciliation Report gives a transaction-level view of what was processed and closed, while the Settlement Report summarises what was settled and when.
All transactions processed and closed during the previous day, with fees and settlement amounts.
A summary of settlements performed to your account, generated when settlement events occur.
# Settlement Report
Source: https://docs.momentco.io/documentation/reconciliation-reporting/settlement-report
Merchant Settlement Report file format and structure
The **Merchant Settlement Report** summarises all settlements performed to the merchant during the period the report applies to. The report is generated only when settlement events occur, and details the amount to be settled to the merchant's bank account on the day the report is generated.
## File Name
```
_____.csv
```
* **FILE\_GENERATION\_DATE**: The date the file is generated (YYYYMMDD format, e.g., 20241225).
* **UNIQUE\_FILE\_IDENTIFIER**: A fixed-value string identifying the system that produced the file.
* **PERIOD\_END\_DATE**: The end date of the settlement period (YYYYMMDD format, e.g., 20241225).
**Example:**
```
20241231_Moment_TestMerchant_Settlement_ae5811c4-0fb3-47de-8a9e-f8b298b9525b_20241230.csv
```
## File Body
| **Column Name** | **Field Name** | **Description** | **Value Type/ Format** | **Example Value** |
| - | - | - | - | - |
| **Transaction Type** | transaction\_type | The type of the transaction. | string | Payment, Refund |
| **Transaction Timestamp** | transaction\_timestamp | The date and time when the transaction occurred. | datetime | 2024-01-31T14:30:00Z |
| **Moment Transaction ID** | reconciliation\_id | ID of the transaction on the Moment platform. Use this as a reference when contacting our support team. | string | 673bbe2805368010f36e9bee |
| **Merchant Reference ID** | merchant\_reference\_id | The unique ID referencing the merchant used when creating the transaction on the Moment platform. | string | 672bbe2805368010f36e123e |
| **Payment Method** | payment\_method | Name of the payment method type used when creating the transaction. | string | Capitec Pay |
| **Tender Type** | payment\_method\_tender | Name of the payment method tender used when creating the transaction. | string | Bank Transfer |
| **Customer Reference** | customer\_reference | The unique ID referencing the merchant used when creating the transaction on the Moment platform. | string | 123456789X1 |
| **Country** | country | The country where the funds of the transaction were collected from. | ISO 3166 alpha-2 | ZA |
| **Presentment Currency** | presentment\_currency | The currency of the transaction collected from the payer. | 3-letter ISO 4217 code | ZAR |
| **Presentment Amount** | presentment\_amount | The amount collected from the payer, in presentment currency. | decimal | 27.12 |
| **Settlement Currency** | settlement\_currency | The currency of the payment that will be settled to the Moment merchant. | 3-letter ISO 4217 code | ZAR |
| **Gross Settlement Amount** | gross\_settlement\_amount | The amount of the payment that will be settled to the merchant, in settlement currency. | decimal | 120 |
| **MDR %** | mdr\_percentage | MDR Percentage. | decimal | 10 |
| **MDR** | mdr | Merchant Discount Rate charged to a merchant for the payment processing. | decimal | 12 |
| **Fixed Fee** | fixed\_fee | Fixed fee charged. | decimal | 3 |
| **FX Fee** | fx\_fee | FX Fee. | decimal | 0 |
| **Total Fee** | total\_fee | The total fee charged. | decimal | 15 |
| **Fee Currency** | fee\_currency | The currency of the fee. | 3-letter ISO 4217 code | ZAR |
| **VAT Amount** | vat\_amount | VAT of the total fee amount charged. | decimal | 2.25 |
| **Net Settlement Amount** | net\_settlement\_amount | Net settlement amount that will be settled to Moment merchant. | decimal | 102.75 |
| **Settlement Batch ID** | settlement\_batch\_id | The batch id of the settlement. | string | MOM-XXXXX-2024-05-25 |
# Support
Source: https://docs.momentco.io/documentation/support
Get help with your Moment integration
The Moment support team is here to help with integration questions, account issues, and anything else you need to get up and running.
Email us at [support@momentco.net](mailto:support@momentco.net).
When reaching out, including the following helps us resolve your issue faster:
* Your merchant ID and environment (test or live)
* The API endpoint and request method
* The error message or HTTP status code received
* The `Request-Id` response header value, if available
***
## Self-Service Resources
Before reaching out, these pages resolve the most common issues:
* [Error Codes](/api-reference/getting-started/errors) β HTTP status codes, application error codes, and decline codes with remediation steps
* [Authentication](/api-reference/getting-started/authentication) β API key types, test vs live modes, and security practices
* [Integration Testing](/api-reference/getting-started/testing) β Test cards and scenarios for simulating payments and errors
* [Webhook Verification](/api-reference/webhooks/verification) β Validating event signatures and handling retries
# Organisation structure
Source: https://docs.momentco.io/guides/company-structure
The Client Portal groups everything under two levels: your **organisation** and its **business units**. Every organisation contains one or more business units. Transactions, reports, and settings in the portal are scoped to the business unit currently selected.
## Organisation
An **organisation** is the top-level account in the Client Portal. It represents your business as a whole, and every business unit you operate sits underneath it.
## Business unit
A **business unit** is the processing account that all transactions and settlements belong to. Most merchants operate a single business unit. Merchants running multiple entities, brands, or regions can have several business units under one organisation.
* An **organisation** can have one or many **business units**.
* A **business unit** belongs to exactly one **organisation**.
* Transactions, reports, and settings in the Client Portal are scoped to a single **business unit** at a time.
If your organisation has more than one business unit, API requests must specify which one they apply to. Pass the business unit identifier in the `Moment-Business-Unit` header. When your API key is linked to a single business unit the header can be omitted. See [Business Units](/api-reference/getting-started/authentication#business-units) in the authentication guide for the full details.
A business unit must be connected to a legal entity, to meet compliance requirements such as Know Your Business (KYB) checks and applicable local regulations.
***
## Switching accounts
* Switch **organisations** from the switcher at the bottom of the side navigation.
* Switch **business units** from the switcher in the page header, next to the test and live mode switch.
If you only have access to a single organisation or business unit, the corresponding switcher does not appear.
## User roles and access
Access to organisations and business units depends on your role. Different roles grant different permissions within the Client Portal.
If you need a role changed or a new user added, contact your admin, or reach out to the Moment team.
# Overview
Source: https://docs.momentco.io/guides/overview
Step-by-step walkthroughs for the Moment client portal and checkout flows
Step-by-step guides covering the Moment client portal and the customer-facing checkout experience.
***
## Client Portal
Create your Moment account and get set up.
Access your Moment account.
Navigate your dashboard, transactions, and key business insights.
View, filter, and manage completed transactions.
Create and manage hosted payment pages from the portal.
Set up and manage recurring billing schedules from the portal.
View and initiate refunds for succeeded card payments.
Create and manage payment requests, individually or in bulk, from the portal.
***
## Payment Flows
The customer checkout experience across payment methods and entry points.
How customers review and pay bills on the EBP page.
The customer experience on a hosted payment page.
How customers complete a PayShap payment in Checkout and opt in at their bank.
# Checkout
Source: https://docs.momentco.io/guides/payment-flows/checkout
Customers can land on checkout from multiple entry points, including the **Payment Sessions API**, **EBP**, a **Payment Page**, or a **Payment Request**. Checkout then guides them to select a payment method, showing only the options enabled by the merchantβs configuration.
At the top of the checkout page, the customer sees the total amount they are about to pay (for example, **Pay ZAR 600.00**).
Checkout displays only the payment methods enabled by the configuration and supported in the customerβs region.
Available categories may include:
* Cards
* Banks
* EFTs
* Wallets
* Pay-in-person
To proceed, the customer taps a payment method and is redirected to the corresponding payment flow.
## Categories of payment method flows
### **Capitec pay**
Capitec Pay is a bank payment method that allows customers to approve payments securely within the Capitec app.
After selecting **Capitec Pay**, the customer is redirected to Capitecβs web experience.
**Step 1: Enter phone number**
The customer enters their Capitec Pay number to initiate the request.
* The customer can navigate back before initiating the request.
**Step 2: Confirmation**
A confirmation screen indicates that a push notification has been sent to the Capitec app. The customer must approve the payment within 3 minutes.
* Once initiated, the request cannot be cancelled from this flow.
* Checkout polls for status updates until the payment is successful or expires.
### Banks - EFT
EFT allows customers to make payments through their bankβs online or mobile banking.
After selecting **EFT**, the customer proceeds through bank selection and redirection.
**Redirect to bank**
After selecting a bank at the first step of checkout, the customer is redirected to to the bankβs secure online or mobile banking environment to log in and approve the payment. (see FNB example)
Only merchant-enabled and region-supported banks are displayed. Once redirected, the approval process takes place entirely within the bankβs system.
### Wallets
Wallet-based methods such as **SnapScan**, **Zapper**, and **Scan to Pay** follow a redirect flow
**Step 1: Redirect to wallet (e.g. Snapscan)**
The customer is transferred to the wallet app or wallet web experience to review and approve the transaction.
**Step 2: Approval and status handling**
Once the payment request is initiated, it cannot be cancelled from the checkout flow.
Checkout pulls for transaction status and automatically updates until the payment is successful or expires.
After approval (or expiry), the customer is returned to checkout with the final transaction status.
### **Instructions-Only Payment Methods**
Some payment methods are **instructions-only**: instead of completing payment in checkout, the customer is shown clear instructions for completing the payment in an external channel (for example, a voucher flow, USSD, in-app steps, or an offline action).
**Step 1: Instructions**
After selecting an instructions-only method, the customer sees the steps they need to follow to complete the payment. Depending on the method, they may also be redirected to a partner page or provider flow.
**Step 2: Completion and status handling**
Checkout monitors the payment status (where supported) and updates automatically until the payment is successful, failed, or expires. The customer then sees the final status in checkout.
### **Pay in person**
Pay-in-person enables customers to complete payment offline at a supported retailer or agent. [See full list here β](https://docs.momentco.io/documentation/network/in-person-network)
**Step 1: Choose retailer or agent**
The customer selects a merchant-enabled retailer or agent.
Only configured retailers are displayed.
**Step 2: Review payment details**
The customer sees:
* Payment amount
* Merchant name and branding
* Payment deadline (if applicable)
* Barcode or Numerical Code for in-store payment
The barcode or code is presented at the till to complete payment.
## Payment Status
**Successful Payment**
After approval within the relevant flow, the customer is redirected to:
* The merchant-defined success URL, or
* The Moment success screen (see screenshot)
**Failed or Expired Payment**
If the payment fails or expires:
* The customer is returned to checkout.
* A status message is displayed.
* The customer may retry or select a different payment method.
# Electronic Bill Presentment
Source: https://docs.momentco.io/guides/payment-flows/electronic-bill-presentment
The Electronic Bill Payment (EBP) page allows a customer to review their bill and complete payment. This guide describes the customer experience on the page, including how the payment amount behaves, when it can be edited, and which validation rules must be satisfied before proceeding to checkout.
## EBP
Based on the billing data provided by the merchant, when a customer opens the EBP page, they see:
* **Due date**
* **Customer name and reference/customer number**
* **Amount due** (pre-filled)
* **Proceed** button (disabled until a valid amount is entered. If no amount is pre-filled, the customer must enter an amount before proceeding)
* **Custom message** with redirect link
## **Amount Editing Rules**
Whether the amount is editable depends on merchant configuration.
### Paying Less Than the Amount Due
If the customer enters an amount **below the amount due**:
* The **Proceed** button remains disabled.
* The customer must enter an amount that **matches or exceeds** the amount due.
Partial payments are not supported in this configuration.
### Paying Above Maximum Allowed
If the merchant has defined a **maximum allowed payment amount**, and the customer enters a higher value:
* The **Proceed** button remains disabled.
* The customer must reduce the amount to within the allowed limit.
### When Amount Due Is 0.00
If the amount due is **0.00**:
* The customer can still make a payment.
* The **Proceed** button remains disabled until the entered amount meets the **minimum payment amount**.
This enables top-ups or advance payments.
# Paying with PayShap
Source: https://docs.momentco.io/guides/payment-flows/paying-with-payshap
How customers complete a PayShap payment in Checkout, and how to opt in to PayShap at each major bank.
PayShap is an **instructions-only** payment method (see [Instructions-Only Payment Methods](/guides/payment-flows/checkout#instructions-only-payment-methods)). Instead of completing the payment inside Checkout, the customer is shown a reference and an amount, then finishes the payment in their own banking app.
This guide walks customers through completing a PayShap payment in Checkout, and links out to each bank's own instructions for opting in to PayShap and creating a ShapID.
## Step 1: Select PayShap in Checkout
On the **Choose how to pay** screen, PayShap is listed under **Banks**.
* PayShap only appears if the merchant has enabled it and the customer is in a supported region.
## Step 2: Follow the PayShap instructions
Checkout displays four instructions:
1. **Log in to your banking app**: the customer opens their own bank's app (not Moment's).
2. **Select PayShap**: inside their banking app, not in Checkout.
3. **Enter this amount exactly as shown**: the amount must match exactly, for example `ZAR 181.68`.
4. **Copy this ShapID into your banking app**: this is Moment's ShapID for the transaction (for example `momt-30434631710782@investec`), not the customer's own ShapID. The customer can tap the copy icon to copy it.
The amount and ShapID shown are unique to this transaction. The customer must enter them exactly as displayed for the payment to be successful.
While the customer completes the payment in their banking app, Checkout shows **Waiting for your payment** and polls for status. Once the bank confirms the payment, Moment's Checkout updates automatically.
## Step 3: Complete the payment in a banking app
Once customers have the amount and ShapID from Checkout, they need to enter these into their own bank's app. The exact menu names differ by bank. Use the tabs below for bank-specific navigation.
1. Open the **FNB App** and log in.
2. Tap **Pay**, then select **PayShap**.
3. Choose to pay using a **ShapID** and enter the ShapID shown in Checkout.
4. Enter the exact amount shown in Checkout.
5. Review the recipient details shown by the app, then confirm and authorize the payment.
1. Open the **Standard Bank app** and log in.
2. From the home screen, tap **Transact**, then **Pay**.
3. Select the **PayShap** payment option.
4. Enter the ShapID shown in Checkout and tap **Verify**.
5. Once the details are verified, tap **Review**, check that everything matches Checkout, then tap **Confirm**.
1. Open the **Absa Banking App** and log in.
2. Tap **Pay**, then select **PayShap**.
3. Choose **ShapID**, enter the ShapID shown in Checkout, and search for the recipient bank if prompted.
4. Enter the exact amount and the reference shown in Checkout.
5. Confirm the recipient details, then tap **Next**/**Confirm** to send the payment.
1. Open the **Nedbank Money app** and log in.
2. Select the **PayShap** widget, then choose **Pay to ShapID**.
3. Enter the ShapID shown in Checkout.
4. Enter the exact amount shown in Checkout.
5. Review the payment details and tap **Pay**, then confirm via the Approve-It prompt.
## Opting in to PayShap
To pay with PayShap, a customer's bank account needs to be opted in to PayShap already. This is a one-time setup step done directly with their bank, outside of Checkout. If a customer can't find PayShap in their banking app, they likely need to register first.
Registering a ShapID is only required to **receive** PayShap payments. Customers can typically still **make** a PayShap payment (paying Moment's ShapID, as in this flow) once PayShap is enabled on their profile, even without registering their own ShapID, but each bank's own app will prompt for anything further that's needed.
FNB customers can register a ShapID from the **FNB App**: tap **Pay** > **PayShap** > **ShapID**, or from Online Banking under **Payments** > **Add** > **PayShap**.
See FNB's own instructions: [FNB Payments: PayShap](https://www.fnb.co.za/ways-to-bank/digital/fnb-pay.html)
Standard Bank customers can register a ShapID from the app: **Accounts** > **PayShap** > **Manage ShapIDs** > **Add ShapID**.
See Standard Bank's own instructions: [How to receive PayShap payments](https://www.standardbank.co.za/southafrica/personal/products-and-services/ways-to-bank/help-centre/register-for-payshap-payments)
Absa customers can register a ShapID from the Absa Banking App: tap their profile, then **Register your ShapID**.
See Absa's own instructions: [Absa: PayShap](https://www.absa.co.za/self-service/digital-banking-services/payshap/)
Nedbank customers can register a ShapID from the Money app: select the **PayShap** widget, then **Manage ShapID** > **Register a ShapID**, and link an account.
See Nedbank's own instructions: [Instantly send and receive money with PayShap](https://personal.nedbank.co.za/bank/digital-banking/needs/payments/payshap.html)
Bank app navigation changes over time. If a customer can't find one of the menu items above, direct them to their bank's own PayShap page linked in each tab, or their bank's customer support.
# Payment Page
Source: https://docs.momentco.io/guides/payment-flows/payment-page
A customer accessing a Payment Page will see the components defined by the merchant configuration.
This may include:
* A payment amount (fixed or editable)
* Standard required fields such as **Name**, **Email**, or **Phone number**
* Additional required or optional fields configured by the merchant
* Custom input types such as:
* Checkbox selections
* Dropdown menus
* Radio button selections
* Free-form text fields
* A **Proceed to pay** button
The **Proceed to pay** button becomes active only once all required fields are completed and valid.
# Home Page
Source: https://docs.momentco.io/guides/portal/home-page
The Moment Client Portal is your go-to for completed transactions, customers, and key business insights. Let's walk through the features you'll find on your homepage and how to use them to manage your business efficiently.
### Homepage Overview and Navigation
**Account switching:** Switch between your company's business units using the button at the top right corner (if available)
**Payment Insights:**
View the main graph showing TPV from all completed transactions. Apply filters or change the displayed time frameβchoose the last 7 days, the last 30 days, or a custom period.
Check out detailed breakdowns underneath the main graph. See transactions by payment method (in-person, bank transfer, card, wallet, and more), categories, and transaction types. Options will vary depending on the features enabled for your business.
# Login
Source: https://docs.momentco.io/guides/portal/login
**Step 1:** Enter your email address.
**Step 2:** Check your inbox. Youβll receive an email from Moment with a one-time verification code.
**Step 3:** Enter the code from the email to verify your address.
**Step 4:** Enter the code from your two-factor authentication (2FA) app.
Open your preferred authenticator app and use the code generated there.
**Step 5:** Click **Continue**.
Youβll be logged into your merchant account.
# Create & Manage Payment Pages
Source: https://docs.momentco.io/guides/portal/payment-pages
Payment Pages make it easy to accept payments from your customers with just a few clicks. Set up custom payment forms, collect important details, and track every transaction in one place.
### How to Create and Manage a Payment Page
**Step 1:**
View all your existing payment pages in the summary table.
Quickly see which pages are active and ready to accept payments.
**Step 2:** Click **Create New Page** to set up a new payment page.
Add a title: this is what customers will see at checkout.
**Step 3:**
Choose the amount type:
* **Open amount** β let customers enter how much they want to pay
* **Fixed amount** β set a specific price
**Step 4:** Add a clear description so customers understand what the payment is for.
**Step 5:** Choose which customer details to collect.
Use toggles to collect basic details: name, email, and phone number
Add custom fields such as text input, text area, dropdown, checkboxes, or radio buttons
For dropdowns and radio buttons, add the options customers can choose from.
All submitted information will appear in the transaction details.
**Step 6:** Click **Publish** to make your payment page live.
**Step 7:** Preview the page to see exactly how it looks for customers, including all custom fields.
**Step 8:** Copy the payment link and share it:
* Send directly to customers
* Add to your website
* Post on social media
**Step 9:** Track all payments made through your page.
View transaction details, edit the page, or disable it at any time.
Payment Pages make it simple to create, customize, and share payment links β so you can collect payments smoothly and focus on growing your business.
# Create & Manage Payment Requests
Source: https://docs.momentco.io/guides/portal/payment-requests
Payment Request by Moment makes it easy to track, create, and manage payments, whether you're sending individual requests or handling them in bulk. Follow these steps to streamline your payment collection process and keep everything organized in one place.
### Create a Single Payment Request
**Step 1:** Select to create a **single payment request**. Decide if you want to send it now or later.
**Step 2:** If you're sending it right away, pick the **delivery channel** and enter the customer's **phone number**.
**Step 3:** Optionally, add an **External reference ID**. This helps you find the request later but isn't required.
**Step 4:** Enter the **amount**, set the **expiry date** if it shouldn't be the default 24 hours, and (optionally) include **customer information** for your records.
**Step 5:** After creating the request, copy the payment link to share with your customer, or go back to the payment request to edit details.
### Create Bulk Payment Requests
**Step 1:** Choose the **bulk creation** option. Decide whether to send the requests now or later.
**Step 2:** To send requests right away by **SMS**, make sure your upload file includes a **phone number** for each entry.
**Step 3:** If sending later, your file still must have all necessary details like **External reference**, **Amount**, and **Expiry date** (if not the default).
**Step 4:** Upload your file. It will be processed and appear in the **Task center** once ready.
**Step 5:** Search payment requests in bulk by **batch ID** to manage or review their status.
**Step 6:** Review each payment request created in the batch, check the summary, and add missing information one by one if needed, like the delivery channel or the customer's phone number.
**Step 7:** If you need to cancel a payment request, simply select the option to cancel it.
**Step 8:** Monitor your batch uploads in the **Task center**, see their completion status, and download payment requests if necessary.
**Step 9:** Every payment request has a history with statuses.
**Step 10: Task Center and batch statuses**
* **Processing**: The batch has been received and is being validated; the output file is being generated.
* **Ready**: The batch was processed successfully. This means the output file was generated, not that every row succeeded. Check the downloaded file to confirm which payment requests were created and which rows failed.
* **Failed**: The batch could not be processed. Check the file and resubmit, or contact support if this persists.
**Step 11: Downloading a batch**
Download the batch to get payment request links for each row, so you can send them yourself, along with the failure reason for any row that didn't succeed.
### Viewing & Filtering Payment Requests
**Step 1:** Open the **Payment tools** page and find the **Payment Request** tab. The table lists all your payment requests, so you can keep track of every request you've created so far.
**Step 2:** Use the search bar to find payment requests by different identifiers. Filter your list by the **status** of each payment request to narrow down what you need.
**Step 3:** To create a new payment request, choose whether you want to make a **single request** or create them **in bulk**.
Managing payment requests with Moment keeps your collections simple, organized, and trackable, whether it's just one payment or hundreds at once. Enjoy faster, smoother payment collection!
# Refunds
Source: https://docs.momentco.io/guides/portal/refunds
How to view and initiate refunds from the Client Portal
Refunds let you return money to a customer for a payment that already succeeded, either the full amount or part of it, directly from the Client Portal.
* Refunds can be viewed by anyone with Client Portal access, but only an **Admin** can initiate one.
* The payment must have **succeeded**.
* Today, this only works for **card payments**.
## Step 1: Open the payment
Go to **Transactions** in the Client Portal, find the payment you want to refund, and open it. Only card payments that succeeded are eligible for a refund.
## Step 2: Initiate the refund
Open the **Refund** tab in the side panel and click **Initiate refund**.
## Step 3: Fill in the refund details
* **Refund amount**: How much to send back to the customer. This can be the full amount or less (a partial refund). If the payment has already been partially refunded, you can only refund up to what's left. The form shows **Amount paid** and **Total refunded to date** so you can see how much is still available.
* **Full refund**: A shortcut that fills in the refund amount for you, so you don't need to work out the remaining balance yourself.
* **Reason for refund**: A required dropdown so there's always a clear, consistent record of why a refund was issued. This helps with reporting and any later queries about the transaction.
* **Note**: An optional free-text field for any extra context that doesn't fit the standard reason. Useful for support teams reviewing the refund later.
* **Created by**: Automatically filled in with your email address. This isn't editable, so there's always a clear record of who initiated the refund.
Choose a reason from the dropdown:
* **Customer request**: The customer asked for their money back (for example, they changed their mind or no longer want the item or service).
* **Duplicate payment**: The customer was charged more than once for the same purchase.
* **Fraudulent transaction**: The payment wasn't authorised by the actual cardholder.
* **Order cancelled**: The order was cancelled before it could be fulfilled.
* **Product not received**: The customer paid but never received the physical goods.
* **Product not as described**: The customer received the product, but it didn't match what was described.
* **Other**: Anything that doesn't fit the reasons above. Use the Note field to add detail.
Click **Create** once you're done.
## Step 4: Confirm the request
You'll see a confirmation screen letting you know the refund is on its way and will be processed within **3 to 7 business days**. From here, click **See refunds** to jump straight to the Refunds tab and check on its status.
## Viewing all refunds
The **Refunds** tab, next to Payments under Transactions, lists every refund that's been initiated, so you can check on status without opening each payment individually. You can search by Refund ID, filter, or download the list. Each row shows:
* **Date**: When the refund was initiated.
* **Refund ID**: A unique reference for the refund itself, separate from the Payment ID.
* **Payment ID**: Links back to the original payment being refunded.
* **Status**: Succeeded, Processing, or Failed.
* **Refund amount**
* **Type**: Full or Partial.
* **Currency**
## Refund details
Clicking into a row from the Refunds tab opens a **Refund details** panel with:
* **Amount refunded**, its status, and the date and time it happened.
* **Refund details**: The type (Full or Partial), the reason selected when it was created, and the reporter (who initiated it).
* **Payment details**: A link back to the original Payment ID, along with the gross amount of that payment and the country it was made in.
* **Payment method details**: The payment method category and type used (for example, Card), plus the scheme (for example, Mastercard) and issuing bank.
This is a useful place to check exactly how much of a payment has already been refunded, especially if there have been multiple partial refunds against the same payment.
# Signup
Source: https://docs.momentco.io/guides/portal/signup
**Step 1:** Enter your email address to get started.
**Step 2:** Check your inbox. Youβll receive an email from Moment with a one-time verification code.
**Step 3:** Enter the code from the email to verify your address.
**Step 4:** Add your first and last name.
**Step 5:** Set up two-factor authentication (2FA).
Open your preferred authenticator app, scan the QR code, then enter the code generated by the app.
**Step 6:** Click **Continue**.
Your account is now created and youβll be logged into your merchant account for the first time.
# Create & Manage Subscriptions
Source: https://docs.momentco.io/guides/portal/subscriptions
How to set up and manage recurring billing from the Client Portal
Subscriptions let you charge customers automatically on a recurring schedule, without needing to request payment each time. Set the amount, the schedule, and any special conditions once, and Moment takes care of the rest.
## How to create a subscription
Go to **Subscriptions** in the Client Portal to see all existing subscriptions in one view: status, customer, amount, and next charge date at a glance. Click **Create Subscription** to open the setup form.
* Enter the **customer name** this subscription is for, and set the **amount** to be charged for the duration of their subscription.
* Choose how often the subscription should charge:
* **Daily**: repeats every day, or every N days.
* **Weekly**: pick one or more days of the week.
* **Monthly**: pick a day (for example "Every 2nd") or a fixed date.
* **Yearly**: pick one or more months and a day.
* Set the **first charge** date. This is when the subscription begins, and combined with the frequency, determines every future charge date.
* Decide what happens if a charge date falls on a public holiday. This applies whether you're charging on a fixed day of the month or a day of the week: choose to leave it as-is, or move it to the nearest, previous, or next business day.
As an optional feature, you can charge an amount now, in addition to the subscription. Use this if you need to collect a one-off amount, like a joining fee, alongside the first regular charge. Your customer is charged this amount now, in addition to the subscription amount.
You can add metadata as key-value pairs against the subscription, for example an internal plan code, a business unit, or a reference ID from another system. Metadata isn't shown to the customer. It's for your own reference, reconciliation, and reporting. You can add multiple pairs, and edit or remove them later from the subscription's details.
Review your setup on the **Summary** screen before confirming. If your configuration matches what you intended, go ahead and create the subscription, or go back to make changes.
Click **Create** to create the subscription. You'll land on the **Subscription details** screen, showing the schedule, next charge date, and full payment history for that subscription going forward.
## Managing an existing subscription
Click into any subscription from the list to view its details: schedule, status, upcoming charge, and past payment attempts. From here you can **cancel** the subscription permanently.
## Good to know
As you configure a schedule, Moment checks it against a few known patterns and either offers to simplify it or blocks it outright.
### Exact equivalents: nudged, with a one-click switch
If your configuration works out to be mathematically identical to a simpler schedule, you'll see a **Switch to X** button:
| Your setup | Equivalent to |
| - | - |
| Weekly, all 7 days selected | Daily |
| Daily, repeating every 7, 14, 21... days | Weekly |
| Monthly, repeating every 12, 24, 36... months | Yearly |
| Yearly, all 12 months selected | Monthly |
| Yearly, evenly spaced months (for example Jan, Apr, Jul, Oct) | Month, every N (for example every 3 months) |
You can accept the switch or dismiss it and keep your original setup. These are nudges, not requirements.
### Approximate look-alikes: drift warning only, no switch
Some configurations look like they'd match a simpler schedule but don't quite, because months vary in length. These show a warning explaining the drift, with no switch button since there's no exact equivalent to switch to:
* Daily, repeating every 28 to 31 days
* Weekly, repeating every 4 weeks
### Hard guard: the one configuration that's blocked
A yearly schedule with multiple months selected that aren't evenly spaced (for example January and March, but not the months in between) is blocked outright. It isn't a valid yearly pattern. Evenly spaced multi-month yearly schedules aren't blocked; they're handled by the nudge above instead.
### Silent normalization at submit
If you dismiss an exact-equivalent nudge and submit anyway, Moment quietly stores the simplified, equivalent version behind the scenes (for example, yearly on Jan/Apr/Jul/Oct becomes monthly, every 3, in storage). You won't see any visible change, but reporting stays consistent. This only applies to exact equivalents. Approximate look-alikes (day 28 to 31, week 4) are stored exactly as entered, drift and all.
Subscriptions make it easy to set up recurring billing once and let it run, with flexibility to adjust frequency, timing, and one-off amounts as your customers' needs change.
# Transactions
Source: https://docs.momentco.io/guides/portal/transactions
### Transaction List and Detailed Filtering
Go to the transaction list to view all transactions in one place. The table is organized by amount, payment method, status, and more.
**Filter**
Filter transactions by time frameβlast 24 hours, last 7 days, last 30 days, or set a custom period to focus on whatβs most relevant to you.
**Advanced filters**
Search for specific transactions using various identifiers to find exactly what you need quickly. Use advanced filters to narrow transactions by status, payment method, or amount.
**Search**
Search for specific transactions using various identifiers to find exactly what you need quickly. Use Reference ID, Customer Identifier, or any other data point available in Transaction Details.
### Transaction Details
Review the transaction list to see all payments for both existing and new customers. Use the **Customer Identifier** column to distinguish them:
* If a number is present, the payment was made by an existing customer.
* If it shows **N/A**, the payment was made by a new customer through Payment Pages (if set up).
**Transaction details**
Click on any transaction in the list to view its details in a side panel. See the amount, status, timestamp, customer information, payment details, payment method information, and fulfillment status.
For **existing customers**, customer information is already filled in.
For **new customers**, check the **Metadata** section to find the customerβs details, the payment purpose, and the business unit it relates to.
# Flutter
Source: https://docs.momentco.io/libraries-and-sdks/mobile/flutter
Embed secure payment checkout experiences directly into your Flutter mobile application
## Overview
The **Moment Flutter SDK** enables seamless payment integration into any Flutter application. The SDK provides a simple, unified API to handle the entire payment flow through a native bottom sheet experience.
Built with **pure Dart** (minimal dependencies), the SDK works across both **iOS** and **Android** platforms.
***
## Key Features
* **Native Experience** β Bottom sheet presentation feels native on both platforms
* **Flexible Callbacks** β Handle success, failure, cancellation, retry, and expiry events
* **Secure** β Sensitive payment data handled entirely in Moment's secure WebView
* **Minimal Dependencies** β Single package, easy to integrate
* **PCI Compliant** β No card data ever touches your application
***
## How It Works
```mermaid theme={"system"}
sequenceDiagram
participant Customer
participant Flutter App
participant Your Backend
participant Moment API
participant Moment Checkout
participant 3DS / Bank Page
Note over Flutter App: App starts β Moment(key) + moment.checkout() called
Flutter App->>Flutter App: Registers fetchClientToken, fetchSessionStatus & event callbacks
Customer->>Flutter App: Taps "Pay Now"
Flutter App->>Moment Checkout: checkout.launch()
Moment Checkout->>Your Backend: fetchClientToken() fires
Your Backend->>Moment API: POST /collect/payment_sessions (with success_url)
Moment API-->>Your Backend: Returns client_token (JWT)
Your Backend-->>Moment Checkout: Returns client_token
Moment Checkout-->>Customer: Displays checkout UI (Bottom Sheet)
Customer->>Moment Checkout: Submits payment
Moment Checkout->>3DS / Bank Page: Redirects customer
Customer->>3DS / Bank Page: Completes authentication
3DS / Bank Page-->>Flutter App: redirect link redirect?session_id=abc123
Note over Flutter App: SDK detects the redirect β fetchSessionStatus fires
Flutter App->>Your Backend: fetchSessionStatus('abc123') fires
Your Backend->>Moment API: GET /collect/payment_sessions/abc123
Moment API-->>Your Backend: Returns session status
Your Backend-->>Flutter App: Returns { status: 'completed', ... }
Flutter App->>Customer: onSuccess(result) fires β shows confirmation
```
1. **App starts** β `Moment(clientKey: ...)` creates an SDK instance; `moment.checkout()` is called to create a checkout instance with your `fetchClientToken`, `fetchSessionStatus`, and event callbacks (`onSuccess`, `onFailure`, etc.)
2. **Customer taps Pay** β `checkout.launch()` is called
3. **SDK calls `fetchClientToken`** β your callback creates a session on your backend (with a `success_url`) and returns the `client_token`
4. **Checkout opens** in a native bottom sheet
5. **Customer submits payment** β Moment takes over and redirects to the authentication page
6. **Customer completes authentication** on the external page
7. **Customer is redirected back** to your app via link with `?session_id=abc123`
8. **SDK detects the link** and calls your `fetchSessionStatus('abc123')` callback automatically
9. **Your callback fetches the status** from your backend, which queries Moment API
10. **SDK routes to the right handler** β `onSuccess`, `onFailure`, `onExpired`, or `onCancel`
11. **Webhooks** are delivered to your backend regardless of outcome
***
## Installation
### **1. Add Dependencies**
Add the following to your `pubspec.yaml`:
```yaml theme={"system"}
dependencies:
momentpay: latest
http: latest # or dio, depending on your preferred HTTP client
```
### **2. Import the SDK**
```dart theme={"system"}
import 'package:momentpay/moment_sdk.dart';
```
***
## Platform Configuration
### iOS
Add to `ios/Runner/Info.plist`:
```xml theme={"system"}
io.flutter.embedded_views_preview
```
### Android
Add to `android/app/src/main/AndroidManifest.xml`:
```xml theme={"system"}
```
Ensure minimum SDK version in `android/app/build.gradle`:
```gradle theme={"system"}
android {
defaultConfig {
minSdkVersion 20
}
}
```
***
## **Quick Start**
### **1. Initialize the SDK**
Call `Moment(clientKey: ...)` once when your app starts to create an SDK instance. Then call `moment.checkout()` with your fetch callbacks and event handlers β it returns a checkout instance, and the SDK calls your callbacks automatically at the right moments, so you never manage tokens or session IDs manually.
**Backend endpoints:**
```javascript server.js theme={"system"}
// Called by fetchClientToken when checkout.launch() is triggered
app.post('/api/create-payment-session', async (req, res) => {
const response = await fetch('https://api.momentpay.net/collect/payment_sessions', {
method: 'POST',
headers: {
'Authorization': 'Bearer sk_test_your_secret_key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: 10000, // Amount in cents (e.g., R100.00)
currency: 'ZAR',
type: 'one_time',
options: {
checkout_options: {
presentation_mode: { mode: 'embedded' },
success_url: 'yourapp://checkout?session_id={SESSION_ID}', // deep link back to app
},
},
}),
});
const { client_token } = await response.json();
res.json({ client_token });
});
// Called by fetchSessionStatus after customer returns via deep link
app.get('/api/session-status', async (req, res) => {
const sessionId = req.query.session_id;
const response = await fetch(
`https://api.momentpay.net/collect/payment_sessions/${sessionId}`,
{ headers: { 'Authorization': 'Bearer sk_test_your_secret_key' } }
);
const session = await response.json();
res.json({ status: session.status, ...session });
});
```
```python server.py theme={"system"}
import requests
from flask import Flask, request, jsonify
app = Flask(__name__)
SECRET_KEY = 'sk_test_your_secret_key'
# Called by fetchClientToken when checkout.launch() is triggered
@app.route('/api/create-payment-session', methods=['POST'])
def create_payment_session():
response = requests.post(
'https://api.momentpay.net/collect/payment_sessions',
headers={
'Authorization': f'Bearer {SECRET_KEY}',
'Content-Type': 'application/json',
},
json={
'amount': 10000, # Amount in cents (e.g., R100.00)
'currency': 'ZAR',
'type': 'one_time',
'options': {
'checkout_options': {
'presentation_mode': {'mode': 'embedded'},
'success_url': 'yourapp://checkout?session_id={SESSION_ID}', # deep link back to app
},
},
},
)
data = response.json()
return jsonify({'client_token': data['client_token']})
# Called by fetchSessionStatus after customer returns via deep link
@app.route('/api/session-status', methods=['GET'])
def get_session_status():
session_id = request.args.get('session_id')
response = requests.get(
f'https://api.momentpay.net/collect/payment_sessions/{session_id}',
headers={'Authorization': f'Bearer {SECRET_KEY}'},
)
session = response.json()
return jsonify({'status': session['status'], **session})
```
```go server.go theme={"system"}
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
const secretKey = "sk_test_your_secret_key"
const momentAPI = "https://api.momentpay.net"
// Called by fetchClientToken when checkout.launch() is triggered
func createPaymentSession(w http.ResponseWriter, r *http.Request) {
payload, _ := json.Marshal(map[string]any{
"amount": 10000, // Amount in cents (e.g., R100.00)
"currency": "ZAR",
"type": "one_time",
"options": map[string]any{
"checkout_options": map[string]any{
"presentation_mode": map[string]string{"mode": "embedded"},
"success_url": "yourapp://checkout?session_id={SESSION_ID}", // deep link back to app
},
},
})
req, _ := http.NewRequest("POST", momentAPI+"/collect/payment_sessions", bytes.NewBuffer(payload))
req.Header.Set("Authorization", "Bearer "+secretKey)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
var result map[string]any
json.NewDecoder(resp.Body).Decode(&result)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]any{"client_token": result["client_token"]})
}
// Called by fetchSessionStatus after customer returns via deep link
func getSessionStatus(w http.ResponseWriter, r *http.Request) {
sessionID := r.URL.Query().Get("session_id")
req, _ := http.NewRequest("GET", fmt.Sprintf("%s/collect/payment_sessions/%s", momentAPI, sessionID), nil)
req.Header.Set("Authorization", "Bearer "+secretKey)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
w.Header().Set("Content-Type", "application/json")
w.Write(body)
}
func main() {
http.HandleFunc("/api/create-payment-session", createPaymentSession)
http.HandleFunc("/api/session-status", getSessionStatus)
http.ListenAndServe(":8080", nil)
}
```
```javascript functions/index.js theme={"system"}
const { onCall } = require('firebase-functions/v2/https');
const axios = require('axios');
// Called by fetchClientToken β creates a payment session
exports.createPaymentSession = onCall(async (request) => {
const { amount, currency } = request.data;
const { data } = await axios.post(
'https://api.momentpay.net/collect/payment_sessions',
{
amount,
currency,
type: 'one_time',
options: {
checkout_options: {
presentation_mode: { mode: 'embedded' },
success_url: 'yourapp://checkout?session_id={SESSION_ID}', // deep link back to app
},
},
},
{
headers: {
Authorization: `Bearer ${process.env.MOMENT_SECRET_KEY}`,
'Content-Type': 'application/json',
},
}
);
return { client_token: data.client_token };
});
// Called by fetchSessionStatus β retrieves session status after deep link redirect
exports.getSessionStatus = onCall(async (request) => {
const { sessionId } = request.data;
const { data } = await axios.get(
`https://api.momentpay.net/collect/payment_sessions/${sessionId}`,
{ headers: { Authorization: `Bearer ${process.env.MOMENT_SECRET_KEY}` } }
);
return { status: data.status, ...data };
});
```
```typescript supabase/functions/create-payment-session/index.ts theme={"system"}
import { serve } from 'https://deno.land/std/http/server.ts';
// Invoked by fetchClientToken
serve(async () => {
const response = await fetch('https://api.momentpay.net/collect/payment_sessions', {
method: 'POST',
headers: {
Authorization: `Bearer ${Deno.env.get('MOMENT_SECRET_KEY')}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: 10000,
currency: 'ZAR',
type: 'one_time',
options: {
checkout_options: {
presentation_mode: { mode: 'embedded' },
success_url: 'yourapp://checkout?session_id={SESSION_ID}', // deep link back to app
},
},
}),
});
const { client_token } = await response.json();
return new Response(JSON.stringify({ client_token }), {
headers: { 'Content-Type': 'application/json' },
});
});
```
```typescript supabase/functions/get-session-status/index.ts theme={"system"}
import { serve } from 'https://deno.land/std/http/server.ts';
// Invoked by fetchSessionStatus
serve(async (req) => {
const { session_id } = await req.json();
const response = await fetch(
`https://api.momentpay.net/collect/payment_sessions/${session_id}`,
{ headers: { Authorization: `Bearer ${Deno.env.get('MOMENT_SECRET_KEY')}` } }
);
const session = await response.json();
return new Response(JSON.stringify({ status: session.status, ...session }), {
headers: { 'Content-Type': 'application/json' },
});
});
```
```javascript handler.js theme={"system"}
// Called by fetchClientToken β POST /api/create-payment-session
exports.createPaymentSession = async (event) => {
const response = await fetch('https://api.momentpay.net/collect/payment_sessions', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.MOMENT_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: 10000,
currency: 'ZAR',
type: 'one_time',
options: {
checkout_options: {
presentation_mode: { mode: 'embedded' },
success_url: 'yourapp://checkout?session_id={SESSION_ID}', // deep link back to app
},
},
}),
});
const { client_token } = await response.json();
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ client_token }),
};
};
// Called by fetchSessionStatus β GET /api/session-status?session_id=abc123
exports.getSessionStatus = async (event) => {
const sessionId = event.queryStringParameters?.session_id;
const response = await fetch(
`https://api.momentpay.net/collect/payment_sessions/${sessionId}`,
{ headers: { Authorization: `Bearer ${process.env.MOMENT_SECRET_KEY}` } }
);
const session = await response.json();
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ status: session.status, ...session }),
};
};
```
**Flutter β initialize once on app start:**
```dart main.dart theme={"system"}
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:momentpay/momentpay/moment_sdk.dart';
final moment = Moment(clientKey: 'pk_your_key_here');
void main() {
final checkout = moment.checkout(
// SDK calls this when checkout.launch() is triggered to get a fresh session token
fetchClientToken: () async {
final response = await http.post(
Uri.parse('https://your-backend.com/api/create-payment-session'),
headers: {'Content-Type': 'application/json'},
);
final data = jsonDecode(response.body);
return data['client_token'];
},
// SDK calls this automatically when a deep link with session_id is detected.
// Happens after a customer returns from a redirect-based payment (3DS, bank redirect, EFT).
// Must return a map with at least: { 'status': 'completed' | 'failed' | 'expired' | 'cancelled' }
fetchSessionStatus: (sessionId) async {
final response = await http.get(
Uri.parse('https://your-backend.com/api/session-status?session_id=$sessionId'),
);
return jsonDecode(response.body);
},
onSuccess: (result) {
debugPrint('Payment successful: $result');
// Navigate to success screen or update UI
},
onFailure: (result) {
debugPrint('Payment failed: $result');
// Show error message
},
onCancel: (result) {
debugPrint('Payment cancelled');
// Allow user to retry
},
onAttemptFailed: (result) {
debugPrint('Payment attempt failed: $result');
// User can retry within the checkout
},
onExpired: (result) {
debugPrint('Payment session expired');
// Request a new session and retry
},
);
runApp(const MyApp());
}
```
```dart main.dart theme={"system"}
import 'package:dio/dio.dart';
import 'package:momentpay/momentpay/moment_sdk.dart';
final _dio = Dio(BaseOptions(baseUrl: 'https://your-backend.com'));
final moment = Moment(clientKey: 'pk_your_key_here');
void main() {
final checkout = moment.checkout(
// SDK calls this when checkout.launch() is triggered to get a fresh session token
fetchClientToken: () async {
final response = await _dio.post('/api/create-payment-session');
return response.data['client_token'];
},
// SDK calls this automatically when a deep link with session_id is detected.
// Happens after a customer returns from a redirect-based payment (3DS, bank redirect, EFT).
// Must return a map with at least: { 'status': 'completed' | 'failed' | 'expired' | 'cancelled' }
fetchSessionStatus: (sessionId) async {
final response = await _dio.get(
'/api/session-status',
queryParameters: {'session_id': sessionId},
);
return response.data;
},
onSuccess: (result) {
debugPrint('Payment successful: $result');
// Navigate to success screen or update UI
},
onFailure: (result) {
debugPrint('Payment failed: $result');
// Show error message
},
onCancel: (result) {
debugPrint('Payment cancelled');
// Allow user to retry
},
onAttemptFailed: (result) {
debugPrint('Payment attempt failed: $result');
// User can retry within the checkout
},
onExpired: (result) {
debugPrint('Payment session expired');
// Request a new session and retry
},
);
runApp(const MyApp());
}
```
```dart main.dart theme={"system"}
import 'package:firebase_core/firebase_core.dart';
import 'package:cloud_functions/cloud_functions.dart';
import 'package:momentpay/momentpay/moment_sdk.dart';
final moment = Moment(clientKey: 'pk_your_key_here');
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
final createSession = FirebaseFunctions.instance.httpsCallable('createPaymentSession');
final getStatus = FirebaseFunctions.instance.httpsCallable('getSessionStatus');
final checkout = moment.checkout(
// SDK calls this when checkout.launch() is triggered to get a fresh session token
fetchClientToken: () async {
final result = await createSession.call({'amount': 10000, 'currency': 'ZAR'});
return result.data['client_token'];
},
// SDK calls this automatically when a deep link with session_id is detected.
// Happens after a customer returns from a redirect-based payment (3DS, bank redirect, EFT).
// Must return a map with at least: { 'status': 'completed' | 'failed' | 'expired' | 'cancelled' }
fetchSessionStatus: (sessionId) async {
final result = await getStatus.call({'sessionId': sessionId});
return result.data;
},
onSuccess: (result) {
debugPrint('Payment successful: $result');
// Navigate to success screen or update UI
},
onFailure: (result) {
debugPrint('Payment failed: $result');
// Show error message
},
onCancel: (result) {
debugPrint('Payment cancelled');
// Allow user to retry
},
onAttemptFailed: (result) {
debugPrint('Payment attempt failed: $result');
// User can retry within the checkout
},
onExpired: (result) {
debugPrint('Payment session expired');
// Request a new session and retry
},
);
runApp(const MyApp());
}
```
```dart main.dart theme={"system"}
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:momentpay/momentpay/moment_sdk.dart';
final moment = Moment(clientKey: 'pk_your_key_here');
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Supabase.initialize(
url: 'https://your-project.supabase.co',
anonKey: 'your-anon-key',
);
final supabase = Supabase.instance.client;
final checkout = moment.checkout(
// SDK calls this when checkout.launch() is triggered to get a fresh session token.
// Invokes a Supabase Edge Function named 'create-payment-session'.
fetchClientToken: () async {
final response = await supabase.functions.invoke('create-payment-session');
return response.data['client_token'];
},
// SDK calls this automatically when a deep link with session_id is detected.
// Happens after a customer returns from a redirect-based payment (3DS, bank redirect, EFT).
// Must return a map with at least: { 'status': 'completed' | 'failed' | 'expired' | 'cancelled' }
fetchSessionStatus: (sessionId) async {
final response = await supabase.functions.invoke(
'get-session-status',
body: {'session_id': sessionId},
);
return response.data;
},
onSuccess: (result) {
debugPrint('Payment successful: $result');
// Navigate to success screen or update UI
},
onFailure: (result) {
debugPrint('Payment failed: $result');
// Show error message
},
onCancel: (result) {
debugPrint('Payment cancelled');
// Allow user to retry
},
onAttemptFailed: (result) {
debugPrint('Payment attempt failed: $result');
// User can retry within the checkout
},
onExpired: (result) {
debugPrint('Payment session expired');
// Request a new session and retry
},
);
runApp(const MyApp());
}
```
```dart main.dart theme={"system"}
import 'dart:convert';
import 'package:amplify_flutter/amplify_flutter.dart';
import 'package:amplify_api/amplify_api.dart';
import 'package:momentpay/momentpay/moment_sdk.dart';
final moment = Moment(clientKey: 'pk_your_key_here');
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Amplify.addPlugin(AmplifyAPI());
await Amplify.configure(amplifyconfig);
final checkout = moment.checkout(
// SDK calls this when checkout.launch() is triggered to get a fresh session token
fetchClientToken: () async {
final restOperation = Amplify.API.post(
'/api/create-payment-session',
apiName: 'PaymentApi',
);
final response = await restOperation.response;
final data = jsonDecode(response.decodeBody());
return data['client_token'];
},
// SDK calls this automatically when a deep link with session_id is detected.
// Happens after a customer returns from a redirect-based payment (3DS, bank redirect, EFT).
// Must return a map with at least: { 'status': 'completed' | 'failed' | 'expired' | 'cancelled' }
fetchSessionStatus: (sessionId) async {
final restOperation = Amplify.API.get(
'/api/session-status',
apiName: 'PaymentApi',
queryParameters: {'session_id': sessionId},
);
final response = await restOperation.response;
return jsonDecode(response.decodeBody());
},
onSuccess: (result) {
debugPrint('Payment successful: $result');
// Navigate to success screen or update UI
},
onFailure: (result) {
debugPrint('Payment failed: $result');
// Show error message
},
onCancel: (result) {
debugPrint('Payment cancelled');
// Allow user to retry
},
onAttemptFailed: (result) {
debugPrint('Payment attempt failed: $result');
// User can retry within the checkout
},
onExpired: (result) {
debugPrint('Payment session expired');
// Request a new session and retry
},
);
runApp(const MyApp());
}
```
### **2. Open Checkout**
```dart checkout.dart theme={"system"}
// SDK automatically calls fetchClientToken to get a fresh token, then opens checkout
checkout.launch(context: context);
```
***
## **API Reference**
### **Moment(clientKey)**
Factory constructor β creates an isolated SDK instance scoped to your publishable key. Returns an object with product namespaces (`checkout`, and more in the future).
| Parameter | Type | Required | Description |
| - | - | - | - |
| `clientKey` | `String` | Yes | Your publishable key (`pk_test_...` or `pk_live_...`). Determines sandbox vs. production environment. |
Throws immediately if `clientKey` is invalid.
```dart theme={"system"}
final moment = Moment(clientKey: 'pk_test_abc123');
```
***
### **moment.checkout(config)**
Creates and returns a checkout instance for this SDK. Typically called in `main()` or your root widget's `initState()`. Returns a `checkout` object with `launch` and `close` methods.
| Parameter | Type | Required | Description |
| - | - | - | - |
| `fetchClientToken` | `Future Function()` | Yes | Async function returning a fresh client token. Called by the SDK when `checkout.launch()` is invoked. |
| `fetchSessionStatus` | `Future