Card Tokenization with Secure Fields
Use the Stitch Secure Fields SDK to securely capture card details and tokenize them for future or recurring payments. Tokenization returns a card ID (token): A reusable reference you can store and use to initiate subsequent transactions, without requiring users to re-enter card details on returning payment journeys.
Capture Card Details
To securely collect card details, integrate the Stitch Secure Fields SDK into your client-side checkout.
At a high level:
<StitchSecureForm>wraps one or more<StitchSecureField>components- On form completion, the SDK returns all card details
- Sensitive values (number, cvc) are returned encrypted so their plaintext value never touches your infrastructure
- You pass these encrypted values to your server to tokenize the card
See the full Secure Fields integration guide here.
Tokenize a Card
You can save a card in two ways, depending on who will initiate future charges:
| If... | Use this flow |
|---|---|
| The customer is present and will authenticate each future payment (e.g. "remember my card") | Save for Future Customer-Initiated Transactions (CIT) |
| You need to charge the card later without the customer present (e.g. subscriptions, scheduled billing, etc.) | Set Up Merchant-Initiated Transactions (MIT) |
Customer-Initiated Transactions (CIT)
Use this flow to store a card for future payments where the customer is present and authenticates each charge. This is the right choice for "remember my card" checkout experiences.
Use the initiateTransaction mutation with storeOnFile: true to verify and tokenize the card (with or without a charge). The response will include a reusable card.id that you can store for future customer-initiated payments.
The initiateTransaction mutation requires a client token with the scope transaction_initiate. Use the GraphQL API URL https://api.stitch.money/graphql.
- Verify Card
- Verify and Charge Card
To verify a card, specify a zero amount within the initateTransaction mutation.
To verify and charge a card, specify a non-zero amount within the initateTransaction mutation.
Merchant-Initiated Transactions (MIT)
Use this flow when you need to charge the card in the future without the customer present — for example, subscriptions, scheduled billing, or account top-ups.
To set up an MIT mandate, include recurringPayment in your initiateTransaction request with source: "payer". This records the customer's consent during this initial (customer-present) step. The agreement.reference must be unique and remain consistent across all future charges in the recurring series.
The response will include a reusable card.id that you can store for future customer-initiated payments.
The initiateTransaction mutation requires a client token with the scope transaction_initiate. Use the GraphQL API URL https://api.stitch.money/graphql.
- Set Up MIT Mandate (No Initial Charge)
- Charge, Store and Set Up MIT Mandate
To verify a card for an MIT mandate, specify a zero amount within the initateTransaction mutation.
To verify and charge a card for an MIT mandate, specify a non-zero amount within the initateTransaction mutation.
- The
nonce(4096-character limit) must be unique per request. - The
externalReference(4096-character limit) can link the request to your order or payment reference. - Card details (
encryptedPan,encryptedSecurityCode, etc.) come from the Secure Fields SDK. - For MIT mandates:
recurringPayment.agreement.referencemust be unique and consistent across the recurring series; usesource: "payer"for this initial customer-initiated step.
Provide Payer information
The payerInformation object should be specified with information of the payer on your system's records. The set of provided information is used to increase the efficacy of fraud risk checks done by Stitch. All possible inputs may be found found in the API reference.
At a minimum, the payerId should always be specified within a request. This may be any internal identifier that always uniquely identifies users across Stitch requests.
Handle 3D Secure (3DS) Authentication
For flows that involve a transaction, the card issuer may require the customer to complete 3D Secure authentication. When this happens, the transaction is returned in a pending state with an interactionUrl. Redirect the customer to this URL to complete 3DS.
Rendering the 3DS Interaction
There are two ways to display the 3DS authentication flow to the user:
- Inside an iframe (recommended): This keeps the user on your checkout page and allows you to control the experience while safely handling errors or timeouts.
- As a full-page redirect: This navigates the user away from your site to the interaction URL and returns them via a redirect, but offers less control and no built-in recovery if something goes wrong.
1. Displaying the 3DS Flow Inside an iframe (Recommended)
Stitch recommends displaying the 3DS interaction inside an iframe, as this keeps the user within your checkout experience and provides safer recovery paths if the issuing bank’s challenge page fails to load or times out.
When using an iframe, the interaction url returned from the Stitch API must be loaded directly as the iframe’s src:
<iframe src="https://3ds.stitch.money/2ce31de7-a30d-4f3b-bfaa-cd06cb30db79"></iframe>
When the user finishes the 3DS flow, the interaction page running inside the iframe will notify your parent window using a postMessage event.
Listen for the message in your hosting page:
window.addEventListener('message', (event) => {
if (event.data?.type === 'finished') {
const { flow, externalReference, id: stitchId } = event.data;
// e.g. close modal, update UI, confirm transaction, etc.
}
});
The following fields are available on the message event:
| Field | Description |
|---|---|
type | finished if the 3DS flow is complete |
id | The Stitch ID of the transaction |
flow | The 3DS interaction type of the transaction: challenge or frictionless |
externalReference | The external reference of the transaction |
status | The final status of the transaction after the user's interaction: TransactionSuccess or TransactionFailure |
statusReason | The status reason, if applicable to the transaction status |
We strongly recommend displaying the 3DS interaction within an overlay modal that sits on top of your checkout page. The modal should include a visible close or cancel button so that users always have a safe way to exit if the issuing bank’s challenge fails or times out. It should present the 3DS page inside its own isolated iframe container to keep the interaction clearly separated from the rest of your UI and it should offer a retry mechanism so users can attempt the authentication again if anything goes wrong. This approach ensures that customers are never left stuck or redirected to a broken page and gives you full control over the overall experience.
As an alternative, you may embed the iframe directly into the body of your page. If you choose this approach, you should still provide a clear retry or recovery option in case the challenge fails and you must ensure that users always have a safe and obvious way to exit the flow. This helps maintain a smooth checkout experience even without a modal.
2. Displaying the 3DS Flow as a full-page redirect
Alternatively, you can redirect the user to the 3DS interaction URL. In this flow, your checkout page is replaced entirely by the 3DS challenge screen and the user completes the authentication directly on that page.
To initiate the flow, you simply take the interaction url returned by the Stitch API and perform a standard browser redirect to it (either by setting window.location.href in your frontend or by submitting a form to it).
To redirect the user back to your site once the 3DS flow is completed, you must supply a redirect_uri parameter during the initial redirect. For example, if you want the user returned to https://example.com/payment, you would append the following query string to the interaction URL: ?redirect_uri=https%3A%2F%2Fexample.com%2Fpayment.
The final URL you redirect the user to should look similar to:
https://3ds.stitch.money/2ce31de7-a30d-4f3b-bfaa-cd06cb30db79?redirect_uri=https%3A%2F%2Fexample.payment
The URL specified as the redirect_uri must be secure i.e. an HTTPS URL.
Once the user has successfully completed the interaction and the payment has been processed, they will be redirected back to your specified redirect_uri with the following query parameters.
| Parameter | Description |
|---|---|
id | The Stitch ID of the transaction |
flow | The 3DS interaction type of the transaction: challenge or frictionless |
externalReference | The external reference of the transaction |
status | The final status of the transaction after the user's interaction: TransactionSuccess or TransactionFailure |
statusReason | The status reason, if applicable to the transaction status |
Transaction Statuses
Flows that initiate a transaction can return the following states:
| Status | Description |
|---|---|
| TransactionPending | The transaction has been initiated, but an interaction (e.g. 3DS) is required. An associated reason is returned. |
| TransactionSuccess | The transaction completed successfully. If 3DS was not required, this may be returned immediately. The card.id in the response is the stored token. |
| TransactionFailure | The transaction failed. An associated reason is returned. |
Failure reasons
The TransactionFailure status indicates that the card transaction failed to be initiated, and includes a reason explaining the cause.
Potential failure reasons are detailed below:
| Reason | Description |
|---|---|
| authorizationFailed | The transaction was declined or blocked. |
| authorizationNotFinalised | The transaction could not be processed by the acquirer. |
| blockedByFraudChecks | The transaction was blocked due to fraud checks. |
| downstreamProviderError | The transaction could not be processed due to downstream error. |
| exceedsCardWithdrawalLimit | The transaction was declined due to withdrawal limits exceeded. |
| insufficientFunds | The transaction was declined due to insufficient funds. |
| internalServerError | The transaction could not be processed due to a server error. |
| invalidCardError | The transaction was declined due to an expired card. |
| invalidConfigurationError | The client has invalid or missing configuration. |
| invalidTransactionError | The transaction could not be processed due to invalid data. |
| secure3dDeclined | The user has declined 3DS authentication. |
| secure3dLookupFailed | 3DS authentication attempt could not be initiated. |
| secure3dNotCompleted | The user has not completed 3DS authentication. |
| tokenDecryptionError | The payment token could not be decrypted. |
Subscribe to Webhooks
Webhooks for transactions can be subscribed to by running the clientWebhookAdd mutation, to receive transaction webhook events. The transaction webhook will include details of the tokenized card in the card object.
If the subscription is successfully created, the body returned by the request will look similar to the sample in the Example Response
tab in widget above.
For more information on receiving webhook events, listing active webhook subscriptions, unsubscribing from webhooks and validating signed webhook subscriptions, please visit the Webhooks page.
Webhook Statuses
The transaction webhook will be dispatched for each of the following status updates:
TransactionSuccessTransactionFailure
Example Payload
{
"data": {
"amount": {
"currency": "ZAR",
"quantity": "1"
},
"card": {
"bin": "41111111",
"cardHolderName": "Joe Soap",
"expiryMonth": 12,
"expiryYear": 2024,
"first6": "41111111",
"id": "Y2FyZC85YWY4OGE4MS05ZjNhLTRlNDItYWRiYy04ZTA1M2Q1YTM3M2U=",
"issuer": {
"name": "capitec",
"country": "ZA"
},
"last4": "1111",
"maskedPan": "411111******1111",
"network": "Visa",
"type": "Credit"
},
"createdAt": "2023-05-10T12:22:42.865Z",
"eci": "05",
"externalReference": "79261d16-c53b-48eb-9019-dc9cfb6c5126",
"id": "Y2FyZHRyYW5zYWN0aW9uLzQwNDMxRTY5LTNERjctNEIyQS1CNDY0LURFNTQwNDc0QkMxQw==",
"nonce": "abb1c3b7-b39b-4a4c-93cb-3bbb363a3171",
"originalAmount": {
"currency": "ZAR",
"quantity": "1"
},
"paymentRequestId": "RRFyZHRyYW5zYWN0aW9uLzQwNDMxRTY5LTNERjctNEIyQS1CNDY0LURFNTQwNDc0QkMxQw==",
"retrievalReferenceNumber": "508714102541",
"secure3dDecision": "skip",
"secure3dDecisionReason": "clientSpecified",
"status": "SUCCESS",
"statusReason": null,
"type": "CARD",
"updatedAt": "2023-05-10T12:22:42.865Z"
},
"datetime": "2023-05-10T12:22:42.865Z",
"id": "transaction:status:success:40431E69-3DF7-4B2A-B464-DE540474BC1C",
"type": "transaction"
}
Initiating a Transaction
Use the returned card ID (e.g. card.id from initiateTransaction) to initiate transactions for one-off or recurring payments.
Delete a Card Token
When a user unlinks a card from your platform, you should delete the stored token over the Stitch API so that transactions can no longer be initiated with that card.
Initiate a deleteCard mutation request to the GraphQL API URL https://api.stitch.money/graphql to delete a stored card token. The mutation returns a boolean to indicate the result of the operation.
After a successful deletion, the card token can no longer be used to initiate transactions.
Test card numbers
Our sandbox environment allows simulating success and failure cases when initiating a card transaction, without requiring real debit or credit card details.
You may use the below card numbers on the card input screen to trigger the corresponding scenario.
For all cards numbers, any future date for the expiry date (in the format MM/YY), as well as any 3-digit value for the CVC, will process as expected.
| Scenario | Card Number |
|---|---|
| 3DS successful and payment completed | Any valid card number, such as 4032035421088592 |
| Transaction fails during 3DS verification | 4004462059871392 |
| Transaction fails during payment authorization | 4032033425469975 |
| Transaction fails due to insufficient funds | 4005519200000004 |
| Transaction fails due to exceeding withdrawal limit | 5284989416854933 |
| Transaction fails due to downstream provider error | 4787692003020026 |