Rest API
Stitch provides an alternative REST interface to create payment requests for different payment methods. View the relevant guide for complete information on each payment method integration, including handling interaction URLs/redirects and webhooks.
Integration Overview
Follow the steps described in the client token guide to obtain a client token with the client_paymentrequest scope.
Use the Stitch REST API URL https://api.stitch.money/v2 for all requests on test and live clients. Standard HTTP status conventions are used for all API responses.
Payment Request Creation
A POST request is used to create a payment request for one or more payment methods. The payment request allows you to specify the amount, merchant details, payer information, and any other relevant references. The paymentMethods object indicates which payment methods are enabled and includes any method-specific information required to process the payment
The payer object should be specified with stored information on the payer. The provided information is used to increase the efficacy of fraud risk checks done by Stitch. At a minimum, the identifier should always be specified within a request.
Initiate a POST request to create a token with the following request body:
curl -X POST 'https://api.stitch.money/v2/payment-requests' \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-d '{
"amount": {
"currency": "ZAR",
"quantity": 1
},
"externalReference": "TestPayment",
"expireAt": "2026-12-31T00:00:00Z",
"payer": {
"identifier": "PAYER-001",
"identifyingDocument": {
"type": "identity_document",
"country": "ZA",
"number": "0401016727083"
},
"mobileNumber": "0711234567",
"email": "hello@stitch.money",
"fullName": "Lilo Pelekai"
},
"metadata": {
"deliveryMethod": "delivery"
},
"paymentMethods": {
"eft": {
"enabled": true,
"payerReference": "PayerRef",
"beneficiaryReference": "Stitch",
"beneficiary": {
"name": "Lilo",
"bank": "absa",
"accountNumber": "123456789"
},
"capitecPay": {
"enabled": true
},
"absaEft": {
"enabled": true
}
},
"card": {
"enabled": true
},
"crypto": {
"enabled": false
}
}
}'
| Field | Type | Description | |
|---|---|---|---|
amount.currency | string | The payment request currency. Possible values: ZAR | |
amount.quantity | numeric | The payment request value. | |
merchantId | string (optional) | The Stitch resource identifier of a pre-registered sub-merchant on your client. When supplied, the payment is routed and settled against the merchant's configuration. Mutually exclusive with paymentMethods.eft.beneficiary — supply one or the other, not both. | |
externalReference | string (optional) | A custom unique identifier associated with the request | |
expireAt | string | An ISO 8601 date that defines the expiration for the request | |
restrictPayerBank | string (optional) | Restricts the payer to a single bank when paying via Pay by Bank. When set, the payer can only pay from the specified bank. Must be a valid PayerBankId value. | |
paymentMethods | object | An object specifying the payment methods enabled and their configuration. | |
metadata | object | Used for additional metadata relating to the request. Nested objects must be stringified JSON objects. | |
splitSettlements | array (optional) | Split payment instructions for an individual transaction or batch collection. See the integration guide for full field reference. |
Specifying a Merchant
The merchantId field is an optional Stitch-supplied merchant identifier that routes the payment to a pre-registered merchant on your client. This is typically used in multi-merchant configurations (e.g. marketplaces or aggregator integrations), where funds for the payment should be associated with a specific sub-merchant or registered entity rather than the default merchant configured on your client.
When supplied, the value must reference a merchant that has been pre-provisioned by Stitch against your client. An example value is shown below:
bWVyY2hhbnQvNzhjMzc2NjUtZTgyNi00MGEyLWJiZDUtOTQ1YWNlYjYzMTc1
The merchantId value is a Stitch resource identifier (not your acquirer-assigned MID). Each pre-registered merchant may be configured for a different subset of payment methods. Requirements per merchant will be aligned with you during onboarding.
Payer Information Fields
| Field | Type | Description | |
|---|---|---|---|
identifier | string (optional) | The client's internal identifier for the payer. | |
identifyingDocument | object (optional) | The payer's identification document. Required for Capitec Pay Verified Flow. | |
identifyingDocument.type | string (optional) | Type of identifying document. Possible values: identity_document, passport, temporary_residence. | |
identifyingDocument.country | string (optional) | The country code of the document (e.g., ZA). | |
identifyingDocument.number | string (optional) | The document number. | |
mobileNumber | string (optional) | The payer's contact number. | |
email | string (optional) | The payer's email address. | |
fullName | string (optional) | The payer's full name. | |
accountCreatedDate | string (optional) | The ISO date when the payer's account was created with the client. |
Payment Method Fields
Note that Pay by Bank is enabled on payment requests by default. Set paymentMethods.eft.enabled to false to create a payment request without Pay by Bank enabled. Capitec Pay and ABSA Pay require that eft is enabled and must be explicitly enabled.
eft Fields
| Field | Type | Description | |
|---|---|---|---|
eft | object (optional) | Pay by Bank payment method configuration. Enabled by default. | |
eft.enabled | boolean (optional) | Whether Pay by Bank is enabled. Defaults to true. | |
eft.payerReference | string (optional) | The payer's bank statement reference. Required for Pay by Bank. | |
eft.beneficiaryReference | string (optional) | The beneficiary's bank statement reference. | |
eft.beneficiary | object (optional) | The beneficiary's destination account. Required for Pay by Bank when merchantId is not supplied at the top level of the request. Mutually exclusive with merchantId — supply one or the other, not both. | |
eft.beneficiary.name | string | The account holder's name. | |
eft.beneficiary.bank | string | The beneficiary’s bank. Must be a valid BankBeneficiaryBankId value. | |
eft.beneficiary.accountNumber | string | The beneficiary's account number. | |
eft.capitecPay | object (optional) | Configuration for Capitec Pay (when supported). | |
eft.capitecPay.enabled | boolean (optional) | Whether Capitec Pay is enabled. Defaults to false. | |
eft.absaEft | object (optional) | Configuration for ABSA Pay (when supported). | |
eft.absaEft.enabled | boolean (optional) | Whether ABSA Pay is enabled. Defaults to false. | |
card Fields
| Field | Type | Description | |
|---|---|---|---|
card | object (optional) | Card payment method configuration. | |
card.enabled | boolean (optional) | Whether card payments are enabled. Defaults to false. | |
crypto Fields
| Field | Type | Description | |
|---|---|---|---|
crypto | object (optional) | Cryptocurrency payment method configuration. | |
crypto.enabled | boolean (optional) | Whether crypto payments are enabled. Defaults to false. | |
The response includes the payment request id, status and relevant request information.
{
"id": "cGF5cmVxLzNiOTU1MDYwLWJkMTAtNGI1Mi05ZGMyLTg1OTE3OWJkMDIwOA==",
"amount": {
"currency": "ZAR",
"quantity": 1
},
"externalReference": "TestPayment",
"expireAt": "2025-12-31T00:00:00",
"status": "pending",
"interaction": {
"type": "redirect",
"url": "https://secure.stitch.money/connect/payment-request/3b955060-bd10-4b52-9dc2-859179bd0208"
}
}
Restricting the Payer's Bank
Set the optional top-level restrictPayerBank field to limit Pay by Bank to a single bank — the payer will only be able to pay from the specified bank. It must be a valid PayerBankId value. Note that this does not affect other payment methods that may be enabled on the payment request.
The example below restricts the payer to Capitec.
curl -X POST 'https://api.stitch.money/v2/payment-requests' \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-d '{
"amount": {
"currency": "ZAR",
"quantity": 1
},
"externalReference": "TestPayment",
"expireAt": "2026-12-31T00:00:00Z",
"restrictPayerBank": "capitec",
"payer": {
"identifier": "PAYER-001",
"identifyingDocument": {
"type": "identity_document",
"country": "ZA",
"number": "0401016727083"
},
"mobileNumber": "0711234567",
"email": "hello@stitch.money",
"fullName": "Lilo Pelekai"
},
"metadata": {
"deliveryMethod": "delivery"
},
"paymentMethods": {
"eft": {
"enabled": true,
"payerReference": "PayerRef",
"beneficiaryReference": "Stitch",
"beneficiary": {
"name": "Lilo",
"bank": "absa",
"accountNumber": "123456789"
},
"capitecPay": {
"enabled": true
},
"absaEft": {
"enabled": true
}
}
}
}'
Surface URL and Handle Callback
The URL returned by the API needs a redirect_uri on an allowlisted domain,
appended as a query string parameter. Send your customer to that URL and they'll be guided
through the payment request. Test clients have these domains allowlisted by default:
The allowlist covers whole domains, so any path on an allowlisted domain works. If https://example.com is on your
allowlist, then https://example.com/payment is a valid redirect_uri, and the
full URL you expose to your customer looks like this:
https://secure.stitch.money/connect/payment-request/2b068bd5-6a5a-42e1-8a45-673cb3ede612?
redirect_uri=https%3A%2F%2Fexample.com%2Fpayment
To add or remove a domain, reach out to the Stitch team.
Production clients can't use an insecure (http) redirect_uri:
Once your customer completes or cancels the payment request, they're redirected back
to the redirect_uri with these query string parameters:
| Field | Description | Type |
|---|---|---|
id | The unique Stitch ID of this payment request. | ID |
status | complete if the payment request was completed successfully, closed if the customer closed the UI, or failed if something went wrong. | String |
externalReference | The value you supplied in the externalReference field when creating the payment request. Use it to match the payment request against your own records. | String |
Use the id to retrieve the final payment request status from the Stitch API, and to match
against incoming webhooks.
Don't use status for database operations — anyone can tamper with a query string parameter. To protect yourself from a fake payload sent to your redirect or webhook endpoint:
- Treat webhooks as your source of truth. They're always sent by Stitch.
- Use signed webhooks, so you can verify the signature on every incoming payload.
Webhooks also mean you still learn the final status of a payment request if the redirect never arrives — for example, if the request times out.
Alternative Redirect URI on Failure
Optionally, you can append a failure_redirect_uri to the URL returned from the API — e.g. ?redirect_uri=https%3A%2F%2Fexample.com%2Fpayment&failure_redirect_uri=https%3A%2F%2Fexample.com%2Fpayment%2Ffailure.
If this parameter is supplied, users will only be redirected to the redirect_uri when the status is complete, and to the failure_redirect_uri when the status is closed or failed. In other words, users that successfully complete a payment are directed to the redirect_uri, and those that exit without completing a payment are directed to the failure_redirect_uri.
The failure_redirect_uri receives the same query parameters and is subject to the same validations as the redirect_uri, including requiring the secure https:// protocol for production clients, and being validated against the configured redirect URI whitelist for your client.
Using Multiple Internal Redirect URIs
failure_redirect_uri only splits two ways. For anything finer-grained, branch on status yourself.
If different outcomes should land your customer on different pages, keep a single allowlisted redirect domain and branch to your internal URLs from there. An example is shown below, in NodeJS:
const status = params.status;
switch (status) {
case "complete":
redirect("/success");
break;
case "closed":
case "failed":
redirect("/retry");
break;
default:
break;
}
Payment Request Expiry
It is highly recommended that an expireAt field is supplied in the request body. At the specified
date and time, the payment request status will automatically transition to state expired if the payment request is not yet completed.
Payment Request Statuses
The table below describes the different statuses for a payment request. A payment request is created with status pending.
| Status | Description |
|---|---|
| completed | The user has completed the payment and is a final state. |
| pending | The user hasn't yet completed the payment initiation request, or they exited Stitch SafeLink before fulfilling the payment. |
| cancelled | The payment request was manually cancelled by the client. |
| expired | The payment request has expired while awaiting user interaction. |
Retrieving Payment Request Status
Initiate a GET request to retrieve a specific request by ID:
curl -X GET 'https://api.stitch.money/v2/payment-requests/cGF5cmVxLzNiOTU1MDYwLWJkMTAtNGI1Mi05ZGMyLTg1OTE3OWJkMDIwOA==' \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
The response object is identical in structure to the one returned by the POST request used to create a payment request.
{
"id": "cGF5cmVxLzNiOTU1MDYwLWJkMTAtNGI1Mi05ZGMyLTg1OTE3OWJkMDIwOA==",
"amount": {
"currency": "ZAR",
"quantity": 1
},
"externalReference": "TestPayment",
"expireAt": "2025-12-31T00:00:00",
"merchantId": "bWVyY2hhbnQvNzhjMzc2NjUtZTgyNi00MGEyLWJiZDUtOTQ1YWNlYjYzMTc1",
"status": "completed"
}
The merchantId is the same base64-encoded value supplied on the create request — see Specifying a Merchant. If the payment request was created without a merchantId, the field is omitted from the response.
Payment Request Cancellation
Payment requests that are not completed or expired i.e. in a terminal state can be cancelled before fulfilment.
Cancelled payment requests are not processed nor can be processed and have a cancelled status. Note that a payment request with an in-flight transaction cannot be cancelled.
Initiate a POST request to cancel a payment request. The payment request id is passed as a path parameter, and the cancellation reason in the request body:
curl -X POST 'https://api.stitch.money/v2/payment-requests/cGF5cmVxLzNiOTU1MDYwLWJkMTAtNGI1Mi05ZGMyLTg1OTE3OWJkMDIwOA==/cancel' \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-d '{
"reason": "duplicate_payment"
}'
| Field | Location | Type | Description |
|---|---|---|---|
id | path | string | The unique Stitch ID |
reason | body | string | The cancellation reason |
The response includes the id of the payment request with status code 200 OK if cancellation is successful. Standard HTTP response codes are returned for non-success scenarios.
{
"id": "cGF5cmVxLzNiOTU1MDYwLWJkMTAtNGI1Mi05ZGMyLTg1OTE3OWJkMDIwOA=="
}
Optionally, when the user clicks the X on the dialog box, the payment initiation request can be automatically cancelled instead. This will subsequently invoke the status update, and corresponding webhook notification.
If you would like this feature enabled on your client, reach out to the Stitch team to have it configured.
Error Handling
Error handling is communicated through standard HTTP status codes. Each response includes a clear status and message, helping you identify the issue and take the appropriate next steps.
Stitch returns an error response with the following format if a body is applicable for the status code, e.g. 400 BAD REQUEST
{
"title": "Bad Request",
"code": "BAD_USER_INPUT",
"detail": "Invalid beneficiary reference"
}
HTTP Status Codes
| Status Code | Meaning | Description |
|---|---|---|
| 200 | OK | The request was successful. The response body contains the payment request resource. |
| 201 | Created | The payment request was successfully created. The response body contains the new payment request resource. |
| 400 | Bad Request | The request was invalid. For example, a required field is missing or malformed. |
| 401 | Unauthorized | Authentication failed or no valid authentication credentials provided. |
| 403 | Forbidden | The authenticated user does not have permission to perform this action. |
| 404 | Not Found | The requested resource (e.g., disbursement) was not found. |
| 409 | Conflict | There is a duplicate nonce or other conflict with the request. |
| 429 | Too Many Requests | Too many requests have been made in a short period (rate limiting). |
| 500 | Internal Server Error | An unexpected error occurred on the Stitch platform. |