Money In

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.

Pre-Auth Request

Request to place a pre-authorisation hold on a customer's vaulted card(s). Funds are reserved on the cardholder's account but not settled until captured. By default the customer's default card is attempted first, cascading through the remaining active cards in priority order until an authorisation succeeds or all cards are exhausted. Pass card_id to authorise a specific card only (no cascade).

Pre-auths 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.

A successful pre-auth returns a transaction_id (tra_), which is then used to capture or reverse the hold. Holds that are neither captured nor reversed expire automatically at expires_at (7 days by default) and the funds are released.

Path

POST /card-vault/{customer_id}/pre-auth

Path ParameterTypeDescriptionExample
customer_idString(32)The customer whose stored cards will be authorised — see customerscus_abc123...

Example (Basic)

Basic example to pre-authorise the customer. The default card is attempted first, then the remaining active cards in priority order.

{
  "item": {
    "amount": "349.00" // amount to place on hold
  },
  "reference": "INV-2026-00042" // your reference; appears on statements and reporting
}

Example (Specific card)

Pre-authorise one specific stored card only. The cascade is disabled — if this card declines, the pre-auth fails.

{
  "item": {
    "amount": "349.00"
  },
  "reference": "INV-2026-00042",
  "card_id": "crd_SFq2E9LskQimPkf2mRqnV" // authorise 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 and hold duration.

{
  "item": {
    "amount": "349.00",
    "title": "Booking deposit", // [optional] carried through as the default invoice line when the hold is captured with an invoice
    "description": "Room 12, 3 nights" // [optional]
  },
  "currency": "ZAR", // [optional] default "ZAR"

  "reference": "INV-2026-00042", // your reference; appears on statements and reporting

  "expires_in_days": 7, // [optional] default 7 — days before an uncaptured hold is automatically released (1–30, scheme limits apply)

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

  // [optional] catalog invoice lines, carried through as the default lines when the hold is captured with an invoice —
  // overrides the default single line from item; can be overridden at capture
  "items": [
    {
      "product_id": "pro_abc123...",
      "qty": 2 // default 1 when omitted
    },
    {
      "product_id": "pro_xyz789...",
      "price_excl": 300.23 // override the default product amount
    }
  ],

  // 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
  },

  "notification": {
    "email": "me@my-email.co.za", // [optional] email notification on the pre-auth outcome
    "webhook_url": "https://merchant.example/webhooks/payments" // webhook on preauth.successful / preauth.failed / preauth.captured / preauth.reversed / preauth.expired
  },

  "metadata": {
    "order_id": "ord_98765" // [optional] custom key/value pairs returned on the pre-auth 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
itemYObjectHold amount and payer-facing detailsSee basic example
item.amountYStringAmount to place on hold349.00
item.titleNStringLine item title, carried through as the default invoice line when the hold is captured with an invoice; defaults to reference when omittedBooking deposit
item.descriptionNStringOptional longer line item descriptionRoom 12, 3 nights
currencyNString(3)ISO 4217 currency code. Default ZARZAR
referenceYString(35)Your reference for this pre-auth; appears on statements and reporting. Must be unique per pre-auth to support idempotent retriesINV-2026-00042
card_idCString(32)Authorise this specific card only; the cascade is disabled. Omit to authorise the customer with cascadecrd_SFq2E9LskQimPkf2mRqnV
expires_in_daysNIntegerDays before an uncaptured hold is automatically released (130; card scheme limits apply). Default 77
itemsNArrayInvoice line items from your catalog, carried through as the default lines when the hold is captured with an invoice; overrides the default single line from item. Can be overridden at capture — see productsSee advanced example
items.product_idYStringCatalog product for this linepro_abc123...
items.qtyNIntegerQuantity for this line item (default 1)2
items.price_exclNNumberOverride the default product amount (excluding tax)300.23
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]
signatureNStringOptional passphrase signature for verifying the payloadsecret-key-for-payload
notification.emailNStringEmail address to notify on the pre-auth outcomeme@my-email.co.za
notification.webhook_urlNStringWebhook URL for the pre-auth lifecycle eventshttps://merchant.example/webhooks/payments
metadataNObjectCustom key/value metadata returned on the pre-auth and webhooks{"order_id": "ord_98765"}

Response Body

A pre-auth (pre_) 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. The transaction_id of the successful attempt is the identifier you pass to the capture and reverse endpoints.

Example where the default card declined and the second card was authorised:

{
    "status": true,
    "result": {
      "id": "pre_Wt5RmK2xPqLv9nYcE7aBz",
      "customer_id": "cus_abc123...",
      "amount": "349.00",
      "currency": "ZAR",
      "reference": "INV-2026-00042",
      "pre_auth_status": "AUTHORISED",
      "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": "AUTHORISED"
        }
      ],
      "expires_at": "2026-07-13T10:15:30Z",
      "created_at": "2026-07-06T10:15:30Z"
    }
}

Example where all cards declined:

{
    "status": false,
    "result": {
      "id": "pre_Rk8NwT4yQmXz2pLvE9aBc",
      "customer_id": "cus_abc123...",
      "amount": "349.00",
      "currency": "ZAR",
      "reference": "INV-2026-00042",
      "pre_auth_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"
    }
}

Response Parameters

Field
Type
Description
Example
statusBooleanWhether the pre-auth succeededtrue
result.idString(32)Unique pre-auth identifier wrapping all cascade attemptspre_Wt5RmK2xPqLv9nYcE7aBz
result.customer_idString(32)Associated customer IDcus_abc123...
result.amountStringAmount placed on hold349.00
result.currencyString(3)ISO 4217 currency codeZAR
result.referenceString(35)Your reference for this pre-authINV-2026-00042
result.pre_auth_statusENUMOverall pre-auth state: AUTHORISED, FAILED, CAPTURED, REVERSED, or EXPIRED, see lookupsAUTHORISED
result.card_idString(32)The card that was ultimately authorised; null when the pre-auth failedcrd_9kLmXw3RtYqPz8vNcE2aB
result.transaction_idString(32)The authorised transaction — pass this to the capture and reverse endpoints; null when the pre-auth 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 pre-authtrue
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.expires_atStringISO timestamp when the uncaptured hold is automatically released2026-07-13T10:15:30Z
result.created_atStringISO timestamp when the pre-auth was created2026-07-06T10:15:30Z

Capture Request

Capture (settle) a previously authorised hold by passing the transaction_id returned by the pre-auth request. The full held amount is captured by default; pass amount for a partial capture — any remaining held funds are automatically released. A pre-auth can be captured once, and only while its status is AUTHORISED.

Path

POST /card-vault/pre-auth/{transaction_id}/capture

Path ParameterTypeDescriptionExample
transaction_idString(32)The authorised transaction returned by the pre-auth requesttra_pr6CvR_4pvWwmgQ4y3dtY

Example (Full capture)

Capture the full held amount. An empty body (or {}) is valid.

{
  "reference": "INV-2026-00042-CAP" // [optional] defaults to the pre-auth reference
}

Example (Partial capture with invoice)

Capture part of the hold; the remainder is released back to the cardholder.

When generating an invoice, the line items default to the item / items supplied on the pre-auth request. Pass a single item object or an items array here to override them — see Checkout Link for the same pattern. Overriding is recommended on partial captures so the invoice lines match the captured amount. When no item.title or items were supplied on either call, the invoice shows a single line using the pre-auth reference.

{
  "amount": "299.00", // [optional] must be <= the held amount; omit to capture in full
  "reference": "INV-2026-00042-CAP", // [optional] defaults to the pre-auth reference

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

  // Invoice line items — overrides the item / items supplied on the pre-auth;
  // pass item for a single free-form line, or items for catalog lines (mutually exclusive)
  "items": [
    {
      "product_id": "pro_abc123...",
      "qty": 2 // default 1 when omitted
    },
    {
      "product_id": "pro_xyz789...",
      "price_excl": 300.23 // override the default product amount
    }
  ],

  // Issue and optionally email an invoice for the captured amount
  "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 capture outcome
    "webhook_url": "https://merchant.example/webhooks/payments" // [optional] overrides the webhook URL supplied on the pre-auth
  }
}

Request Parameters

Field
RequiredType
Description
Example
amountNStringAmount to capture; must be less than or equal to the held amount. Omit to capture the full hold. Any uncaptured remainder is released299.00
referenceNString(35)Your reference for the capture; defaults to the pre-auth referenceINV-2026-00042-CAP
itemNObjectSingle free-form invoice line when generating an invoice; overrides the lines supplied on the pre-auth. Mutually exclusive with items{"title": "Booking deposit"}
item.titleCStringLine item title shown on the invoiceBooking deposit
item.descriptionNStringOptional longer line item descriptionRoom 12, 3 nights
itemsNArrayInvoice line items from your catalog when generating an invoice; overrides the lines supplied on the pre-auth. Mutually exclusive with item — see productsSee partial capture example
items.product_idYStringCatalog product for this linepro_abc123...
items.qtyNIntegerQuantity for this line item (default 1)2
items.price_exclNNumberOverride the default product amount (excluding tax)300.23
signatureNStringOptional passphrase signature for verifying the payloadsecret-key-for-payload
invoice.is_generateNBooleanWhether to generate an invoice for the captured amounttrue
invoice.is_sendNBooleanWhether to send the paid invoice to the customertrue
notification.emailNStringEmail address to notify on the capture outcomeme@my-email.co.za
notification.webhook_urlNStringWebhook URL for the capture outcome; overrides the URL supplied on the pre-authhttps://merchant.example/webhooks/payments

Response Body

{
    "status": true,
    "result": {
      "id": "pre_Wt5RmK2xPqLv9nYcE7aBz",
      "customer_id": "cus_abc123...",
      "transaction_id": "tra_pr6CvR_4pvWwmgQ4y3dtY",
      "card_id": "crd_9kLmXw3RtYqPz8vNcE2aB",
      "pre_auth_status": "CAPTURED",
      "amount": "349.00",
      "captured_amount": "299.00",
      "released_amount": "50.00",
      "currency": "ZAR",
      "reference": "INV-2026-00042-CAP",
      "invoice_id": "inv_k2Jd8sLqPz...",
      "captured_at": "2026-07-08T09:20:00Z"
    }
}

Response Parameters

Field
Type
Description
Example
statusBooleanWhether the capture succeededtrue
result.idString(32)The pre-auth this capture belongs topre_Wt5RmK2xPqLv9nYcE7aBz
result.customer_idString(32)Associated customer IDcus_abc123...
result.transaction_idString(32)The captured transactiontra_pr6CvR_4pvWwmgQ4y3dtY
result.card_idString(32)The card the funds were captured fromcrd_9kLmXw3RtYqPz8vNcE2aB
result.pre_auth_statusENUMCAPTURED on success, see lookupsCAPTURED
result.amountStringOriginal held amount349.00
result.captured_amountStringAmount settled299.00
result.released_amountStringRemainder of the hold released back to the cardholder50.00
result.currencyString(3)ISO 4217 currency codeZAR
result.referenceString(35)Reference used for the captureINV-2026-00042-CAP
result.invoice_idString(32)Invoice identifier; present when invoice.is_generate was trueinv_k2Jd8sLqPz...
result.captured_atStringISO timestamp when the capture completed2026-07-08T09:20:00Z

Reverse Request

Release a previously authorised hold without settling any funds, by passing the transaction_id returned by the pre-auth request. The full held amount is returned to the cardholder's available balance. A pre-auth can only be reversed while its status is AUTHORISED; expired holds are released automatically.

Path

POST /card-vault/pre-auth/{transaction_id}/reverse

Path ParameterTypeDescriptionExample
transaction_idString(32)The authorised transaction returned by the pre-auth requesttra_pr6CvR_4pvWwmgQ4y3dtY

Example

An empty body (or {}) is valid.

{
  "reason": "Booking cancelled by customer", // [optional] recorded against the reversal for reporting

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

  "notification": {
    "email": "me@my-email.co.za", // [optional] email notification on the reversal outcome
    "webhook_url": "https://merchant.example/webhooks/payments" // [optional] overrides the webhook URL supplied on the pre-auth
  }
}

Request Parameters

Field
RequiredType
Description
Example
reasonNStringOptional reason recorded against the reversal for reportingBooking cancelled by customer
signatureNStringOptional passphrase signature for verifying the payloadsecret-key-for-payload
notification.emailNStringEmail address to notify on the reversal outcomeme@my-email.co.za
notification.webhook_urlNStringWebhook URL for the reversal outcome; overrides the URL supplied on the pre-authhttps://merchant.example/webhooks/payments

Response Body

{
    "status": true,
    "result": {
      "id": "pre_Wt5RmK2xPqLv9nYcE7aBz",
      "customer_id": "cus_abc123...",
      "transaction_id": "tra_pr6CvR_4pvWwmgQ4y3dtY",
      "card_id": "crd_9kLmXw3RtYqPz8vNcE2aB",
      "pre_auth_status": "REVERSED",
      "amount": "349.00",
      "released_amount": "349.00",
      "currency": "ZAR",
      "reference": "INV-2026-00042",
      "reversed_at": "2026-07-08T09:20:00Z"
    }
}

Response Parameters

Field
Type
Description
Example
statusBooleanWhether the reversal succeededtrue
result.idString(32)The pre-auth this reversal belongs topre_Wt5RmK2xPqLv9nYcE7aBz
result.customer_idString(32)Associated customer IDcus_abc123...
result.transaction_idString(32)The reversed transactiontra_pr6CvR_4pvWwmgQ4y3dtY
result.card_idString(32)The card the hold was released fromcrd_9kLmXw3RtYqPz8vNcE2aB
result.pre_auth_statusENUMREVERSED on success, see lookupsREVERSED
result.amountStringOriginal held amount349.00
result.released_amountStringAmount released back to the cardholder349.00
result.currencyString(3)ISO 4217 currency codeZAR
result.referenceString(35)Your reference from the original pre-authINV-2026-00042
result.reversed_atStringISO timestamp when the reversal completed2026-07-08T09:20: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

Webhooks are delivered to notification.webhook_url for each pre-auth lifecycle event when that field was supplied. Broader platform webhooks are configured separately if applicable. Webhooks fire once per pre-auth outcome, not per cascade attempt.

Possible event values include preauth.successful, preauth.failed, preauth.captured, preauth.reversed, and preauth.expired, listed under Webhook events.

Webhook Payload

{
  // preauth.successful · preauth.failed · preauth.captured · preauth.reversed · preauth.expired
  "event": "preauth.captured",
  "data": [
    {
      "pre_auth": {
        "id": "pre_Wt5RmK2xPqLv9nYcE7aBz",
        "customer_id": "cus_abc123...",
        "amount": "349.00",
        "captured_amount": "299.00", // present on preauth.captured
        "released_amount": "50.00", // present on preauth.captured / preauth.reversed / preauth.expired
        "currency": "ZAR",
        "reference": "INV-2026-00042-CAP",
        "pre_auth_status": "CAPTURED",
        "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": "CAPTURED"
          }
        ],
        "invoice_id": "inv_k2Jd8sLqPz...",
        "metadata": {
          "order_id": "ord_98765"
        },
        "expires_at": "2026-07-13T10:15:30Z",
        "completed_at": "2026-07-08T09:20:00Z"
      }
    }
  ],
  "created_at": "2026-07-08T09:20:00Z"
}

Webhook Payload Parameters

Field
Type
Description
Example
eventStringType of webhook event that occurredpreauth.captured
dataArrayArray containing pre-auth data...
data.pre_auth.idString(32)Unique pre-auth identifierpre_Wt5RmK2xPqLv9nYcE7aBz
data.pre_auth.customer_idString(32)Associated customer IDcus_abc123...
data.pre_auth.amountStringOriginal held amount349.00
data.pre_auth.captured_amountStringAmount settled; present on preauth.captured299.00
data.pre_auth.released_amountStringAmount released back to the cardholder; present on preauth.captured, preauth.reversed, and preauth.expired50.00
data.pre_auth.currencyString(3)ISO 4217 currency codeZAR
data.pre_auth.referenceString(35)Reference for this pre-auth or captureINV-2026-00042-CAP
data.pre_auth.pre_auth_statusENUMPre-auth state after the event, see lookupsCAPTURED
data.pre_auth.card_idString(32)The card that was authorised; null on failurecrd_9kLmXw3RtYqPz8vNcE2aB
data.pre_auth.transaction_idString(32)The authorised transaction; null on failuretra_pr6CvR_4pvWwmgQ4y3dtY
data.pre_auth.attemptsArrayEvery card attempt in the order tried, including declines...
data.pre_auth.invoice_idString(32)Invoice identifier when invoice.is_generate was true on captureinv_k2Jd8sLqPz...
data.pre_auth.metadataObjectCustom key/value metadata supplied on create{"order_id": "ord_98765"}
data.pre_auth.expires_atStringISO timestamp when the uncaptured hold is automatically released2026-07-13T10:15:30Z
data.pre_auth.completed_atStringISO timestamp when the pre-auth reached the status for this event2026-07-08T09:20:00Z
created_atStringISO timestamp when webhook was created2026-07-08T09:20:00Z

Webhook Events

EventDescriptionTrigger ConditionData Included
preauth.successfulA hold was placed on one of the customer's cardsWhen any cascade attempt (or the specified card_id) is authorisedPre-auth details, authorised transaction, full attempt history, hold expiry
preauth.failedThe pre-auth failed on all attempted cardsWhen all cascade attempts decline, a hard decline aborts the cascade, or the specified card_id declinesPre-auth details, full attempt history with decline codes
preauth.capturedThe hold was settled in full or in partWhen a capture request completesPre-auth details, captured and released amounts, invoice when generated
preauth.reversedThe hold was released without settlementWhen a reverse request completesPre-auth details, released amount
preauth.expiredThe hold expired and was released automaticallyWhen expires_at passes without a capture or reversalPre-auth details, released amount

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.