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.
{
"item": {
"amount": "349.00" // amount to charge
},
"transaction_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.
{
"item": {
"amount": "349.00"
},
"transaction_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 (Invoice line items)
When generating an invoice, by default the invoice shows a single line from item; pass an items array to populate the invoice with catalog line items instead — see Checkout Link for the same pattern. When no item.title is supplied, the line uses the charge transaction_reference.
Single free-form line via item:
{
"transaction_reference": "INV-2026-00042",
"item": {
"amount": "349.00",
"title": "July 2026 subscription", // shown as the invoice line item
"description": "Pro plan, monthly billing" // [optional]
},
"invoice": {
"is_generate": true,
"is_send": true
}
}
Catalog lines via items — reference products with an optional price_excl override:
{
"item": {
"amount": "349.00"
},
"transaction_reference": "INV-2026-00042",
"items": [
{
"product_id": "pro_abc123...",
"qty": 2 // default 1 when omitted
},
{
"product_id": "pro_xyz789...",
"price_excl": 300.23 // override the default product amount
}
],
"invoice": {
"is_generate": true,
"is_send": true
}
}
Example (Advanced)
Advanced example with all options, including cascade controls and invoicing.
{
"item": {
"amount": "349.00",
"title": "July 2026 subscription", // [optional] shown as the invoice line item
"description": "Pro plan, monthly billing" // [optional]
},
"currency": "ZAR", // [optional] default "ZAR"
"transaction_reference": "INV-2026-00042", // your reference; appears on statements and reporting
"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
},
// Issue and optionally email an invoice for this charge — line items come from item / items when supplied (see the invoice line items example)
"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 |
|---|---|---|---|---|
| item | Y | Object | Charge amount and payer-facing details | See basic example |
| item.amount | Y | String | Amount to charge | 349.00 |
| item.title | N | String | Line item title shown on the invoice; defaults to transaction_reference when omitted | July 2026 subscription |
| item.description | N | String | Optional longer line item description | Pro plan, monthly billing |
| currency | N | String(3) | ISO 4217 currency code. Default ZAR | ZAR |
| transaction_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 |
| card_id | C | String(32) | Charge this specific card only; the cascade is disabled. Omit to charge the customer with cascade | crd_SFq2E9LskQimPkf2mRqnV |
| items | N | Array | Invoice line items from your catalog when generating an invoice; overrides the default single line from item — see products | See invoice line items example |
| items.product_id | Y | String | Catalog product for this line | pro_abc123... |
| items.qty | N | Integer | Quantity for this line item (default 1) | 2 |
| items.price_excl | N | Number | Override the default product amount (excluding tax) | 300.23 |
| 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 |
| 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",
"transaction_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": {
"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",
"transaction_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": {
"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": {
"message": "The transaction was declined because the card has expired"
}
}
],
"created_at": "2026-07-06T10:15:35Z"
}
}
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.transaction_reference | String(35) | Your reference for this charge | INV-2026-00042 |
| result.charge_status | ENUM | Overall charge outcome: SUCCESSFUL or FAILED, 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.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",
"transaction_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": {
"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.transaction_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
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.
Card Vault Pre-Auth
Place a pre-authorisation hold on a customer's stored cards server-to-server, then capture or reverse it later using the transaction ID. Attempts the default card first with automatic cascade to fallback cards, or targets a specific card. Supports merchant-initiated (MIT) scheme compliance, partial captures, invoice issuance, and webhook notifications.