Money In

Card Vault Charge

 

Charge a customer's stored cards server-to-server. Attempts the default card first with automatic cascade to fallback cards, or targets a specific card. Supports merchant-initiated (MIT) scheme compliance, invoice issuance, synchronous and asynchronous processing, and webhook notifications.

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 ParameterTypeDescriptionExample
customer_idString(32)The customer whose stored cards will be charged — see customerscus_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
RequiredType
Description
Example
amountYStringAmount to charge349.00
currencyNString(3)ISO 4217 currency code. Default ZARZAR
referenceYString(35)Your reference for this charge; appears on statements and reporting. Must be unique per charge to support idempotent retriesINV-2026-00042
descriptionNStringOptional longer descriptionJuly 2026 subscription
card_idCString(32)Charge this specific card only; the cascade is disabled. Omit to charge the customer with cascadecrd_SFq2E9LskQimPkf2mRqnV
typeNENUMMIT classification for scheme compliance: UNSCHEDULED (default; ad-hoc merchant-initiated) or RECURRING (fixed-schedule billing)UNSCHEDULED
cascadeNObjectCascade behaviour; ignored when card_id is passedSee advanced example
cascade.is_enabledNBooleanWhen false, only the default card is attempted. Default truetrue
cascade.max_attemptsNIntegerCap on the number of cards tried; defaults to all ACTIVE cards3
cascade.card_orderNArrayExplicit card order override; defaults to the default card first, then by priority[crd_SFq2..., crd_9kLm...]
cascade.skip_codesNArrayAdditional 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_asyncNBooleanWhen 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 falsefalse
signatureNStringOptional passphrase signature for verifying the payloadsecret-key-for-payload
invoice.is_generateNBooleanWhether to generate an invoice for the chargetrue
invoice.is_sendNBooleanWhether to send the paid invoice to the customertrue
notification.emailNStringEmail address to notify on the charge outcomeme@my-email.co.za
notification.webhook_urlNStringWebhook URL for the charge outcomehttps://merchant.example/webhooks/payments
metadataNObjectCustom 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
statusBooleanWhether the charge succeededtrue
result.idString(32)Unique charge identifier wrapping all cascade attemptschg_Wt5RmK2xPqLv9nYcE7aBz
result.customer_idString(32)Associated customer IDcus_abc123...
result.amountStringCharge amount349.00
result.currencyString(3)ISO 4217 currency codeZAR
result.referenceString(35)Your reference for this chargeINV-2026-00042
result.charge_statusENUMOverall charge outcome: SUCCESSFUL, FAILED, or PROCESSING (async), see lookupsSUCCESSFUL
result.card_idString(32)The card that ultimately succeeded; null when the charge failedcrd_9kLmXw3RtYqPz8vNcE2aB
result.transaction_idString(32)The successful transaction; null when the charge failedtra_pr6CvR_4pvWwmgQ4y3dtY
result.attemptsArrayEvery card attempt in the order tried, including declines...
result.attempts.sequenceIntegerPosition of this attempt in the cascade1
result.attempts.card_idString(32)Card attemptedcrd_SFq2E9LskQimPkf2mRqnV
result.attempts.is_defaultBooleanWhether this card was the customer's default at the time of the chargetrue
result.attempts.transaction_idString(32)Transaction identifier for this attempttra_x9YtRw2QpLmN8vKcE3aBz
result.attempts.transaction_statusENUMTransaction status, see lookupsDECLINED
result.attempts.error.codeENUMDecline code when the attempt failed, see lookupsINSUFFICIENT_FUNDS
result.attempts.error.messageStringHuman-readable decline reasonThe transaction was declined due to insufficient funds
result.invoice_idString(32)Invoice identifier; present when invoice.is_generate was true and the charge succeededinv_k2Jd8sLqPz...
result.created_atStringISO timestamp when the charge was created2026-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
eventStringType of webhook event that occurredcharge.successful
dataArrayArray containing charge data...
data.charge.idString(32)Unique charge identifierchg_Wt5RmK2xPqLv9nYcE7aBz
data.charge.customer_idString(32)Associated customer IDcus_abc123...
data.charge.amountStringCharge amount349.00
data.charge.currencyString(3)ISO 4217 currency codeZAR
data.charge.referenceString(35)Your reference for this chargeINV-2026-00042
data.charge.charge_statusENUMOverall charge outcome, see lookupsSUCCESSFUL
data.charge.card_idString(32)The card that ultimately succeeded; null on failurecrd_9kLmXw3RtYqPz8vNcE2aB
data.charge.transaction_idString(32)The successful transaction; null on failuretra_pr6CvR_4pvWwmgQ4y3dtY
data.charge.attemptsArrayEvery card attempt in the order tried, including declines...
data.charge.invoice_idString(32)Invoice identifier when invoice.is_generate was true and the charge succeededinv_k2Jd8sLqPz...
data.charge.metadataObjectCustom key/value metadata supplied on create{"order_id": "ord_98765"}
data.charge.completed_atStringISO timestamp when the charge reached its final status2026-07-06T10:15:30Z
created_atStringISO timestamp when webhook was created2026-07-06T10:15:30Z

Webhook Events

EventDescriptionTrigger ConditionData Included
charge.successfulThe charge succeeded on one of the customer's cardsWhen any cascade attempt (or the specified card_id) is authorisedCharge details, successful transaction, full attempt history, invoice when generated
charge.failedThe charge failed on all attempted cardsWhen all cascade attempts decline, a hard decline aborts the cascade, or the specified card_id declinesCharge 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 payload
  • X-Timestamp: Unix timestamp of when the webhook was sent
  • User-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.