Money In

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.

Request

Request to generate a card vault page session URL where a payer can securely store a card, or manage their stored cards. Only cards are supported on this page — no other payment methods are shown.

Every newly stored card is verified with a 3D Secure authenticated transaction. If fee.amount is supplied it is charged as a one-time fee when the card is stored; otherwise a default R2.00 verification amount is charged (an amount is required for 3DS authentication).

Path

POST /card-vault/{customer_id}/link

Path ParameterTypeDescriptionExample
customer_idString(32)The customer to store or manage cards for. The customer must exist — see customerscus_abc123...

Example (Basic)

Basic example to create a link where the payer stores a card. The default R2.00 verification amount is charged for 3DS.

{
  "type": "STORE_CARD",
  "item": {
    "title": "Save your card" // shown on the vault page and payer statement
  }
}

Example (One-time fee)

Charge a one-time fee when the card is stored, instead of the default R2.00 verification amount.

{
  "type": "STORE_CARD",

  "item": {
    "title": "Card activation fee", // shown on the vault page and payer statement
    "description": "A once-off activation fee is charged when your card is saved" // [optional]
  },

  "fee": {
    "amount": "50.00", // one-time fee charged with the 3DS verification; omit to default to "2.00"
    "is_refund": false // [optional] default false — when true, the default R2.00 verification amount is automatically reversed after successful storage; ignored when a custom fee amount is set
  },

  "notification": {
    "email": "me@my-email.co.za", // [optional] email notification when a card is stored
    "webhook_url": "https://merchant.example/webhooks/cards" // webhook on card vault events
  },

  "redirects": {
    "success_url": "https://merchant.example/card-saved?myquery=myparam", // may append &signature=
    "cancel_url": "https://merchant.example/cancel"
  }
}

Example (Manage cards)

Create a link where the payer can view, add, remove, set the default, and reorder their stored cards. Adding a card runs the same 3DS verification flow as STORE_CARD.

{
  "type": "MANAGE_CARDS",

  "item": {
    "title": "Manage your cards"
  },

  "manage": {
    "is_allow_add": true, // [optional] default true — payer can add a new card (runs the R2.00 / fee 3DS flow)
    "is_allow_remove": true, // [optional] default true — removing the default card promotes the next card by priority
    "is_allow_set_default": true // [optional] default true — payer can choose their default card and reorder fallbacks
  },

  "notification": {
    "webhook_url": "https://merchant.example/webhooks/cards" // card.stored · card.removed · card.default_changed · card.reordered
  },

  "redirects": {
    "success_url": "https://merchant.example/cards?myquery=myparam",
    "cancel_url": "https://merchant.example/cancel"
  }
}

Example (Advanced)

Advanced example with all options, including card placement in the cascade order, vault limits, and page customization.

{
  "type": "STORE_CARD",

  "signature": "secret-key-for-payload", // [optional] shared-secret signature for the payload, see the signature section below

  "item": {
    "title": "Save your card",
    "description": "Securely store your card for future payments"
  },

  // Placement of the newly stored card in the customer's cascade order
  "card": {
    "is_default": false, // [optional] default true for the customer's first card, false otherwise; when true, the existing default card is demoted
    "priority": 2 // [optional] explicit fallback position among non-default cards; ignored when is_default is true; defaults to the end of the cascade order
  },

  "fee": {
    "amount": "50.00",
    "is_refund": false
  },

  "notification": {
    "email": "me@my-email.co.za",
    "webhook_url": "https://merchant.example/webhooks/cards"
  },

  "redirects": {
    "success_url": "https://merchant.example/card-saved?myquery=myparam",
    "cancel_url": "https://merchant.example/cancel"
  },

  "settings": {
    "expiry_time": 1440, // minutes until the vault link lapses — omit when the link never expires (default)
    "max_cards": 3, // [optional] cap on stored cards for this customer; the link shows an error state when the customer is at the limit — omit for no limit
    "is_duplicate_check": true // [optional] default true — rejects storing a card whose PAN fingerprint already exists on this customer
  },

  "customization": {
    "button_text": "Save card", // [optional] primary action button label
    "type": "PAGE", // or "EMBED" for iframe-style hosting or in-app card storage
    "is_display_cancel_button": true,
    "confirmation_message": "Your card has been saved", // [optional] shown after successful storage; otherwise use redirects
    "brand": {
      "primary": "#00DC82",
      "secondary": "#CCCCCC"
    }
  }
}

Example (Embed card storage)

Embed card storage into your mobile app or frameworks like React, Angular or Vue.

{
  "type": "STORE_CARD",

  "item": {
    "title": "Save your card"
  },

  "customization": {
    "button_text": "Save card", // [optional]
    "type": "EMBED", // show embedded screen that can be displayed in an iFrame or WebView, this will also send message events instead of a redirect
    "is_display_cancel_button": false, // hide the cancel button, if true will trigger a 'card.cancelled' message
    "brand": {
      "primary": "#00DC82", // brand according to your app
      "secondary": "#CCCCCC"
    }
  }
}

The following message events will be sent to your app via a native bridge; use them to drive the next action:

  • card.loaded
  • card.cancelled
  • card.failed
  • card.stored
  • card.pending
  • card.closed

The following data will be sent in the message on card.stored events to verify the stored card:

{
  "source": "kwik-payments",
  "event": "card.stored",
  "sessionId": "ses_abc123...",
  "customerId": "cus_abc123...",
  "cardId": "crd_abc123...",
  "transactionId": "tra_abc123...",
  "status": "ACTIVE"
}

The following data will be sent in the message on card.failed events:

{
  "source": "kwik-payments",
  "event": "card.failed",
  "sessionId": "ses_abc123...",
  "customerId": "cus_abc123...",
  "transactionId": "tra_abc123...",
  "status": "DECLINED",
  "message": "Your card could not be verified due to insufficient funds"
}

Request Parameters

Fields below appear in the request body examples on this page. Y = required for all requests, C = required or applicable depending on type.

Field
RequiredType
Description
Example
typeYENUMVault mode: STORE_CARD (store a new card) or MANAGE_CARDS (view, add, remove, set default, reorder stored cards)STORE_CARD
itemYObjectPayer-facing details for the vault pageSee basic example
item.titleYStringTitle shown on the vault page and payer statementSave your card
item.descriptionNStringOptional longer description shown on the vault pageSecurely store your card for future payments
cardNObjectPlacement of the newly stored card in the customer's cascade order (STORE_CARD only)See advanced example
card.is_defaultNBooleanMake the new card the customer's default. Defaults to true for the customer's first card, false otherwise. When true, the existing default is demotedfalse
card.priorityNIntegerExplicit fallback position among non-default cards; ignored when is_default is true. Defaults to the end of the cascade order2
fee.amountNStringOne-time fee charged with the 3DS verification when the card is stored. Defaults to 2.00 (an amount is required for 3DS authentication)50.00
fee.is_refundNBooleanWhen true, the default R2.00 verification amount is automatically reversed after successful storage. Ignored when a custom fee.amount is set. Default falsefalse
manageCObjectPayer permissions when type is MANAGE_CARDSSee manage example
manage.is_allow_addNBooleanPayer can add a new card; runs the R2.00 / fee 3DS flow. Default truetrue
manage.is_allow_removeNBooleanPayer can remove stored cards; removing the default promotes the next card by priority. Default truetrue
manage.is_allow_set_defaultNBooleanPayer can choose their default card and reorder fallbacks. Default truetrue
signatureNStringOptional passphrase signature for verifying the payloadsecret-key-for-payload
notification.emailNStringEmail address to notify when a card is storedme@my-email.co.za
notification.webhook_urlNStringWebhook URL for card vault eventshttps://merchant.example/webhooks/cards
redirects.success_urlNStringRedirect after the card is stored or changes are saved; &signature= appended if configuredhttps://merchant.example/card-saved?myquery=myparam
redirects.cancel_urlNStringRedirect if the payer cancelshttps://merchant.example/cancel
settings.expiry_timeNIntegerLink lifetime in minutes; omit so the link does not expire (default)1440
settings.max_cardsNIntegerCap on stored cards for this customer; the link shows an error state when the customer is at the limit. Omit for no limit3
settings.is_duplicate_checkNBooleanRejects storing a card whose PAN fingerprint already exists on this customer, preventing the same card occupying two cascade slots. Default truetrue
customization.button_textNStringPrimary action button labelSave card
customization.confirmation_messageNStringMessage after successful storage; otherwise use redirectsYour card has been saved
customization.typeNENUMCreate a link for a PAGE or pass through EMBED for iframe or in-app card storage. The EMBED type pages will not redirect you but transmit a message via the window postMessage() methodPAGE
customization.is_display_cancel_buttonNBooleanShow cancel controltrue
customization.brand.primaryNStringPrimary hex colour#00DC82
customization.brand.secondaryNStringSecondary hex colour#CCCCCC

Response Body

{
    "status": true,
    "result": {
      "id": "cdl_HVpCeoNys1f22X7QcuWHY",
      "session_id": "ses_G-xkVKoxHgEBrY8suKgR3",
      "customer_id": "cus_abc123...",
      "type": "STORE_CARD",
      "fee_amount": "2.00",
      "currency": "ZAR",
      "link_url": "https://pay.kwik.co.za/card-vault/cs_test_a1b2c3",
      "expires_at": "2026-07-06T12:30:00Z"
    }
}

Response Parameters

Field
Type
Description
Example
statusBooleanWhether the request succeededtrue
result.idString(32)Unique card vault link identifiercdl_HVpCeoNys1f22X7QcuWHY
result.session_idString(32)Session identifier for the vault linkses_G-xkVKoxHgEBrY8suKgR3
result.customer_idString(32)Associated customer IDcus_abc123...
result.typeENUMVault mode for this linkSTORE_CARD
result.fee_amountStringFee or verification amount charged when the card is stored2.00
result.currencyString(3)ISO 4217 currency codeZAR
result.link_urlStringURL to redirect the payer to store or manage cardshttps://pay.kwik.co.za/card-vault/cs_test_a1b2c3
result.expires_atStringISO timestamp when the vault link expires; null when the link does not expire2026-07-06T12:30:00Z

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 card is stored, fails verification, is removed, or the customer's card order changes, a webhook may be delivered to notification.webhook_url when that field was supplied on create. Broader platform webhooks are configured separately if applicable.

Possible event values include card.stored, card.failed, card.removed, and others listed under Webhook events.

Webhook Payload

{
  // card.stored · card.failed · card.removed · card.default_changed · card.reordered · card.link_expired
  "event": "card.stored",
  "data": [
    {
      "card": {
        "id": "crd_SFq2E9LskQimPkf2mRqnV",
        "customer_id": "cus_abc123...",
        "session_id": "ses_G-xkVKoxHgEBrY8suKgR3",
        "brand": "VISA",
        "last_four": "4242",
        "expiry_month": "09",
        "expiry_year": "2029",
        "holder": "J DOE",
        "card_status": "ACTIVE",
        "is_default": false,
        "priority": 2,
        "fee_amount": "2.00",
        "fee_transaction_id": "tra_pr6CvR_4pvWwmgQ4y3dtY", // 3DS verification / fee transaction (the card's CIT anchor for card-on-file charges)
        "created_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 occurredcard.stored
dataArrayArray containing card data...
data.card.idString(32)Unique card identifiercrd_SFq2E9LskQimPkf2mRqnV
data.card.customer_idString(32)Associated customer IDcus_abc123...
data.card.session_idString(32)Vault link session that produced this eventses_G-xkVKoxHgEBrY8suKgR3
data.card.brandENUMCard brand, see lookupsVISA
data.card.last_fourString(4)Last four digits of the card number4242
data.card.expiry_monthString(2)Card expiry month09
data.card.expiry_yearString(4)Card expiry year2029
data.card.holderStringCardholder nameJ DOE
data.card.card_statusENUMCard status, see lookupsACTIVE
data.card.is_defaultBooleanWhether this card is the customer's defaultfalse
data.card.priorityIntegerResolved position in the customer's cascade order2
data.card.fee_amountStringFee or verification amount charged when the card was stored2.00
data.card.fee_transaction_idStringTransaction identifier of the 3DS verification / fee chargetra_pr6CvR_4pvWwmgQ4y3dtY
data.card.created_atStringISO timestamp when the card was stored2026-07-06T10:15:30Z
created_atStringISO timestamp when webhook was created2026-07-06T10:15:30Z

Webhook Events

EventDescriptionTrigger ConditionData Included
card.storedA card was successfully verified and stored in the vaultWhen 3DS authentication and the verification / fee charge succeedCard details, fee amount, fee transaction
card.failedCard storage failedWhen 3DS authentication fails or the verification / fee charge is declinedCard attempt details, error information
card.removedA card was removed from the vaultWhen the payer removes a card on a MANAGE_CARDS link, or via the APIRemoved card details; promoted_card_id when the default changed as a result
card.default_changedThe customer's default card changedWhen the payer or merchant sets a new default cardprevious_card_id and new default card details
card.reorderedThe customer's cascade order changedWhen the payer or merchant reorders fallback cardsFull resolved cascade order
card.link_expiredThe vault link expired without completionWhen the link reaches settings.expiry_time without a card being storedLink details only, no card data

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.