Card Vault Charge
Request
Request to charge a customer's vaulted card(s). By default the customer's default card is attempted first, cascading through the remaining active cards in priority order until a charge succeeds or all cards are exhausted. Pass card_id to charge a specific card only (no cascade).
Charges are processed as merchant-initiated transactions (MIT) — no 3DS challenge is presented. The 3DS authentication from the original card storage (see Card Vault Link) provides the authentication reference for scheme compliance.
Path
POST /card-vault/{customer_id}/charge
| Path Parameter | Type | Description | Example |
|---|---|---|---|
| customer_id | String(32) | The customer whose stored cards will be charged — see customers | cus_abc123... |
Example (Basic)
Basic example to charge the customer. The default card is attempted first, then the remaining active cards in priority order.
{
"amount": "349.00",
"reference": "INV-2026-00042" // your reference; appears on statements and reporting
}
Example (Specific card)
Charge one specific stored card only. The cascade is disabled — if this card declines, the charge fails.
{
"amount": "349.00",
"reference": "INV-2026-00042",
"card_id": "crd_SFq2E9LskQimPkf2mRqnV" // charge this card only; obtain card IDs from the card vault webhooks or the customer's card list
}
Example (Advanced)
Advanced example with all options, including cascade controls, MIT classification, invoicing, and asynchronous processing.
{
"amount": "349.00",
"currency": "ZAR", // [optional] default "ZAR"
"reference": "INV-2026-00042", // your reference; appears on statements and reporting
"description": "July 2026 subscription", // [optional]
"type": "UNSCHEDULED", // [optional] MIT classification: "UNSCHEDULED" (default) or "RECURRING" for fixed-schedule billing
"signature": "secret-key-for-payload", // [optional] shared-secret signature for the payload, see the signature section below
// Cascade behaviour — ignored when card_id is passed
"cascade": {
"is_enabled": true, // [optional] default true — when false, only the default card is attempted
"max_attempts": 3, // [optional] cap on the number of cards tried; defaults to all ACTIVE cards
"card_order": [ // [optional] explicit order override; defaults to: default card first, then by priority
"crd_SFq2E9LskQimPkf2mRqnV",
"crd_9kLmXw3RtYqPz8vNcE2aB"
],
"skip_codes": ["DO_NOT_HONOUR"] // [optional] additional decline codes that abort the cascade; hard declines (e.g. SUSPECTED_FRAUD, STOLEN_CARD, PICKUP_CARD) always abort and cannot be overridden
},
"settings": {
"is_async": false // [optional] default false — when true, the request returns charge_status PROCESSING immediately and the outcome is delivered via webhook
},
// Issue and optionally email an invoice for this charge
"invoice": {
"is_generate": true,
"is_send": true // send paid invoice to the customer when true
},
"notification": {
"email": "me@my-email.co.za", // [optional] email notification on the charge outcome
"webhook_url": "https://merchant.example/webhooks/payments" // webhook on charge.successful / charge.failed
},
"metadata": {
"order_id": "ord_98765" // [optional] custom key/value pairs returned on the charge and webhooks
}
}
Request Parameters
Fields below appear in the request body examples on this page. Y = required for all requests, C = required or applicable depending on usage.
Field | Required | Type | Description | Example |
|---|---|---|---|---|
| amount | Y | String | Amount to charge | 349.00 |
| currency | N | String(3) | ISO 4217 currency code. Default ZAR | ZAR |
| reference | Y | String(35) | Your reference for this charge; appears on statements and reporting. Must be unique per charge to support idempotent retries | INV-2026-00042 |
| description | N | String | Optional longer description | July 2026 subscription |
| card_id | C | String(32) | Charge this specific card only; the cascade is disabled. Omit to charge the customer with cascade | crd_SFq2E9LskQimPkf2mRqnV |
| type | N | ENUM | MIT classification for scheme compliance: UNSCHEDULED (default; ad-hoc merchant-initiated) or RECURRING (fixed-schedule billing) | UNSCHEDULED |
| cascade | N | Object | Cascade behaviour; ignored when card_id is passed | See advanced example |
| cascade.is_enabled | N | Boolean | When false, only the default card is attempted. Default true | true |
| cascade.max_attempts | N | Integer | Cap on the number of cards tried; defaults to all ACTIVE cards | 3 |
| cascade.card_order | N | Array | Explicit card order override; defaults to the default card first, then by priority | [crd_SFq2..., crd_9kLm...] |
| cascade.skip_codes | N | Array | Additional decline codes that abort the cascade instead of trying the next card. Hard declines (SUSPECTED_FRAUD, STOLEN_CARD, PICKUP_CARD) always abort and cannot be overridden — see lookups | [DO_NOT_HONOUR] |
| settings.is_async | N | Boolean | When true, the request returns charge_status PROCESSING immediately and the outcome is delivered via webhook; poll GET /card-vault/{customer_id}/charges/{charge_id} for status. Default false | false |
| signature | N | String | Optional passphrase signature for verifying the payload | secret-key-for-payload |
| invoice.is_generate | N | Boolean | Whether to generate an invoice for the charge | true |
| invoice.is_send | N | Boolean | Whether to send the paid invoice to the customer | true |
| notification.email | N | String | Email address to notify on the charge outcome | me@my-email.co.za |
| notification.webhook_url | N | String | Webhook URL for the charge outcome | https://merchant.example/webhooks/payments |
| metadata | N | Object | Custom key/value metadata returned on the charge and webhooks | {"order_id": "ord_98765"} |
Response Body
A charge (chg_) wraps one or more card transactions (tra_). Every attempt — including declines — is a real transaction with its own identifier, listed in attempts in the order they were tried.
Example where the default card declined and the second card succeeded:
{
"status": true,
"result": {
"id": "chg_Wt5RmK2xPqLv9nYcE7aBz",
"customer_id": "cus_abc123...",
"amount": "349.00",
"currency": "ZAR",
"reference": "INV-2026-00042",
"charge_status": "SUCCESSFUL",
"card_id": "crd_9kLmXw3RtYqPz8vNcE2aB",
"transaction_id": "tra_pr6CvR_4pvWwmgQ4y3dtY",
"attempts": [
{
"sequence": 1,
"card_id": "crd_SFq2E9LskQimPkf2mRqnV",
"is_default": true,
"transaction_id": "tra_x9YtRw2QpLmN8vKcE3aBz",
"transaction_status": "DECLINED",
"error": {
"code": "INSUFFICIENT_FUNDS",
"message": "The transaction was declined due to insufficient funds"
}
},
{
"sequence": 2,
"card_id": "crd_9kLmXw3RtYqPz8vNcE2aB",
"is_default": false,
"transaction_id": "tra_pr6CvR_4pvWwmgQ4y3dtY",
"transaction_status": "SUCCESSFUL"
}
],
"invoice_id": "inv_k2Jd8sLqPz...",
"created_at": "2026-07-06T10:15:30Z"
}
}
Example where all cards declined:
{
"status": false,
"result": {
"id": "chg_Rk8NwT4yQmXz2pLvE9aBc",
"customer_id": "cus_abc123...",
"amount": "349.00",
"currency": "ZAR",
"reference": "INV-2026-00042",
"charge_status": "FAILED",
"card_id": null,
"transaction_id": null,
"attempts": [
{
"sequence": 1,
"card_id": "crd_SFq2E9LskQimPkf2mRqnV",
"is_default": true,
"transaction_id": "tra_x9YtRw2QpLmN8vKcE3aBz",
"transaction_status": "DECLINED",
"error": {
"code": "INSUFFICIENT_FUNDS",
"message": "The transaction was declined due to insufficient funds"
}
},
{
"sequence": 2,
"card_id": "crd_9kLmXw3RtYqPz8vNcE2aB",
"is_default": false,
"transaction_id": "tra_z2QpLvE9aBcRk8NwT4yQm",
"transaction_status": "DECLINED",
"error": {
"code": "EXPIRED_CARD",
"message": "The transaction was declined because the card has expired"
}
}
],
"created_at": "2026-07-06T10:15:35Z"
}
}
When settings.is_async is true, the response returns immediately with charge_status PROCESSING and an empty attempts array; the outcome is delivered via webhook.
Response Parameters
Field | Type | Description | Example |
|---|---|---|---|
| status | Boolean | Whether the charge succeeded | true |
| result.id | String(32) | Unique charge identifier wrapping all cascade attempts | chg_Wt5RmK2xPqLv9nYcE7aBz |
| result.customer_id | String(32) | Associated customer ID | cus_abc123... |
| result.amount | String | Charge amount | 349.00 |
| result.currency | String(3) | ISO 4217 currency code | ZAR |
| result.reference | String(35) | Your reference for this charge | INV-2026-00042 |
| result.charge_status | ENUM | Overall charge outcome: SUCCESSFUL, FAILED, or PROCESSING (async), see lookups | SUCCESSFUL |
| result.card_id | String(32) | The card that ultimately succeeded; null when the charge failed | crd_9kLmXw3RtYqPz8vNcE2aB |
| result.transaction_id | String(32) | The successful transaction; null when the charge failed | tra_pr6CvR_4pvWwmgQ4y3dtY |
| result.attempts | Array | Every card attempt in the order tried, including declines | ... |
| result.attempts.sequence | Integer | Position of this attempt in the cascade | 1 |
| result.attempts.card_id | String(32) | Card attempted | crd_SFq2E9LskQimPkf2mRqnV |
| result.attempts.is_default | Boolean | Whether this card was the customer's default at the time of the charge | true |
| result.attempts.transaction_id | String(32) | Transaction identifier for this attempt | tra_x9YtRw2QpLmN8vKcE3aBz |
| result.attempts.transaction_status | ENUM | Transaction status, see lookups | DECLINED |
| result.attempts.error.code | ENUM | Decline code when the attempt failed, see lookups | INSUFFICIENT_FUNDS |
| result.attempts.error.message | String | Human-readable decline reason | The transaction was declined due to insufficient funds |
| result.invoice_id | String(32) | Invoice identifier; present when invoice.is_generate was true and the charge succeeded | inv_k2Jd8sLqPz... |
| result.created_at | String | ISO timestamp when the charge was created | 2026-07-06T10:15:30Z |
Signature creation
When creating API keys on the dashboard you can download a passphrase key, use it to generate your signature and send it in the signature parameter. The canonicalization and HMAC-SHA256 process is identical across all endpoints — see Checkout Link — Signature creation for Node.js, PHP, C#, Java, and Python examples.
Webhook
When a charge completes or fails, a webhook may be delivered to notification.webhook_url when that field was supplied on create. Broader platform webhooks are configured separately if applicable. Webhooks fire once per charge outcome, not per cascade attempt.
Possible event values include charge.successful and charge.failed, listed under Webhook events.
Webhook Payload
{
// charge.successful · charge.failed
"event": "charge.successful",
"data": [
{
"charge": {
"id": "chg_Wt5RmK2xPqLv9nYcE7aBz",
"customer_id": "cus_abc123...",
"amount": "349.00",
"currency": "ZAR",
"reference": "INV-2026-00042",
"charge_status": "SUCCESSFUL",
"card_id": "crd_9kLmXw3RtYqPz8vNcE2aB",
"transaction_id": "tra_pr6CvR_4pvWwmgQ4y3dtY",
"attempts": [
{
"sequence": 1,
"card_id": "crd_SFq2E9LskQimPkf2mRqnV",
"is_default": true,
"transaction_id": "tra_x9YtRw2QpLmN8vKcE3aBz",
"transaction_status": "DECLINED",
"error": {
"code": "INSUFFICIENT_FUNDS",
"message": "The transaction was declined due to insufficient funds"
}
},
{
"sequence": 2,
"card_id": "crd_9kLmXw3RtYqPz8vNcE2aB",
"is_default": false,
"transaction_id": "tra_pr6CvR_4pvWwmgQ4y3dtY",
"transaction_status": "SUCCESSFUL"
}
],
"invoice_id": "inv_k2Jd8sLqPz...",
"metadata": {
"order_id": "ord_98765"
},
"completed_at": "2026-07-06T10:15:30Z"
}
}
],
"created_at": "2026-07-06T10:15:30Z"
}
Webhook Payload Parameters
Field | Type | Description | Example |
|---|---|---|---|
| event | String | Type of webhook event that occurred | charge.successful |
| data | Array | Array containing charge data | ... |
| data.charge.id | String(32) | Unique charge identifier | chg_Wt5RmK2xPqLv9nYcE7aBz |
| data.charge.customer_id | String(32) | Associated customer ID | cus_abc123... |
| data.charge.amount | String | Charge amount | 349.00 |
| data.charge.currency | String(3) | ISO 4217 currency code | ZAR |
| data.charge.reference | String(35) | Your reference for this charge | INV-2026-00042 |
| data.charge.charge_status | ENUM | Overall charge outcome, see lookups | SUCCESSFUL |
| data.charge.card_id | String(32) | The card that ultimately succeeded; null on failure | crd_9kLmXw3RtYqPz8vNcE2aB |
| data.charge.transaction_id | String(32) | The successful transaction; null on failure | tra_pr6CvR_4pvWwmgQ4y3dtY |
| data.charge.attempts | Array | Every card attempt in the order tried, including declines | ... |
| data.charge.invoice_id | String(32) | Invoice identifier when invoice.is_generate was true and the charge succeeded | inv_k2Jd8sLqPz... |
| data.charge.metadata | Object | Custom key/value metadata supplied on create | {"order_id": "ord_98765"} |
| data.charge.completed_at | String | ISO timestamp when the charge reached its final status | 2026-07-06T10:15:30Z |
| created_at | String | ISO timestamp when webhook was created | 2026-07-06T10:15:30Z |
Webhook Events
| Event | Description | Trigger Condition | Data Included |
|---|---|---|---|
charge.successful | The charge succeeded on one of the customer's cards | When any cascade attempt (or the specified card_id) is authorised | Charge details, successful transaction, full attempt history, invoice when generated |
charge.failed | The charge failed on all attempted cards | When all cascade attempts decline, a hard decline aborts the cascade, or the specified card_id declines | Charge details, full attempt history with decline codes |
Webhook Security
All webhooks are sent with the following headers for verification:
X-Signature: HMAC-SHA256 signature of the payloadX-Timestamp: Unix timestamp of when the webhook was sentUser-Agent:Kwik-Webhooks/1.0
Webhook Response
Your endpoint should respond with a 200 status code to acknowledge receipt. Failed webhooks will be retried up to 3 times with exponential backoff.
Card Vault Link
Create a secure, customizable link session for a customer to store or manage cards in the card vault. Cards are tokenized and verified with 3D Secure authentication, with an optional one-time fee, support for default and fallback card ordering, and webhook notifications.
Payouts
Send money to customer bank accounts and manage outbound transfers. Create single or batch payouts with real-time status tracking, beneficiary management, and comprehensive transaction reporting.