Money In

Checkout Link

 

Create a secure, customizable checkout link session to capture payments with cards, bank transfers, and other payment methods. Supports 3D Secure authentication, cards storage for recurring billing, invoice issuance, customer creation, and webhook notifications.

Request

Request to generate a checkout page session URL

Path

POST /checkout/link

Example (Basic)

Basic example to create a one-time payment link with a fixed amount.

{
  "type": "CHOOSE_WHAT_TO_PAY",
  "item": {
    "title": "My product name",
    "frequency": ["ONE_TIME"], // [optional] default "ONE_TIME"
    "amount": "1200.00" // [optional] preset an amount to pay else an amount field will show for the user to enter
  }
}

Example (Donation form)

An example of a donation form with one-time, monthly and annual subscription options and an amount field the user can select.

Flexible checkouts (CHOOSE_WHAT_TO_PAY) use a single item object for the payer-facing offer. customization.confirmation_message appears after successful payment unless you rely on redirects. Customer handling: send customer_id for an existing record, send a customer object to create or prefill, or omit both and optionally supply customer_fields for extra form fields (name, surname, and email always show).

{
  "type": "CHOOSE_WHAT_TO_PAY",
  // Pass your product / campaign details via item
  "item": {
    "title": "My product name", // Product title, will appear on invoice
    "description": "My product description", // [optional] longer description
    "frequency": ["ONE_TIME", "MONTHLY", "ANNUALLY"], // options the payer may choose among
    "amount_options": [50, 100, 150, 200, 250], // [optional] preset amounts (max five)
    "amount_max": 3000 // [optional] cap when payer enters any amount
  },

  "customization": {
    "button_text": "Donate now", // [optional] primary button label — see Advanced for full options
    "confirmation_message": "Thank you for your donation" // [optional] shown after payment; else use redirects
  },

  "signature": "secret-key-for-payload", // [optional] payload signature when configured

  "notification": {
    "email": "me@my-email.co.za", // [optional] email notifications on successful payment
    "webhook_url": "https://merchant.example/webhooks/payments" // webhook URL for each successful payment
  },

  // Issue and optionally email an invoice after payment
  "invoice": {
    "is_generate": true,
    "is_send": true // send paid invoice to the customer when true
  },

  // If neither customer_id nor customer is passed, show a form — these are extra collectable fields
  "customer_fields": [
    "CONTACT_NUMBER",
    "ID_NUMBER",
    "COMPANY_NAME",
    "COMPANY_REG",
    "VAT_NUMBER",
    "BILLING_ADDRESS",
    "SHIPPING_ADDRESS"
  ]
}

Example (Products, subscriptions and invoices)

Example with a recurring and one-time product to create a subscription with invoices.

{
  "type": "PRODUCTS_AND_SUBSCRIPTIONS",

  "customization": {
    "button_text": "Pay now" // [optional] button text — default label if omitted depends on checkout
  },

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

  "customer_id": "cus_abc123...", // existing customer record; alternatively pass full customer object

  // Catalog lines (one-off and recurring products may be mixed — see Products reference)
  "items": [
    {
      "product_id": "pro_abc123...",
      "qty": 2 // default 1 when omitted
    },
    {
      "product_id": "pro_xyz789..."
    }
  ],

  // Invoice for this checkout / subscriptions created from it
  "invoice": {
    "is_generate": true, // generate invoice for the basket or subscription billing
    "is_send": true // email invoice when paid
  },

  // Redirects after payer finishes or taps cancel
  "redirects": {
    "success_url": "https://merchant.example/success?myquery=myparam", // may append &signature=
    "cancel_url": "https://merchant.example/cancel"
  }
}

Example (Advanced )

Advanced example with example with all options

{
  "type": "PRODUCTS_AND_SUBSCRIPTIONS",

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

  // Inline customer create / prefilled profile (alternative: customer_id)
  "customer": {
    "reference": "CLN240919000001",
    "company_name": null,
    "person_name": "John",
    "person_surname": "Doe",
    "client_type": "RESIDENT_INDIVIDUAL", // [optional]
    "id_type": "SOUTH_AFRICAN_ID",
    "id_number": "8007014800087",
    "email": "johndoe@mail.com",
    "contact_number": "+27831234567",
    "billing_address": {
      "line1": "1 Loop Street",
      "line2": "Floor 5",
      "city_or_town": "Cape Town",
      "province_or_state": "Western Cape",
      "postal_or_zip_code": "8001",
      "country": "ZA"
    },
    "shipping": {
      "contact_name": "Jane Doe",
      "contact_number": "+27820000000",
      "address": {
        "line1": "1 Loop Street",
        "line2": "Floor 5",
        "city_or_town": "Cape Town",
        "province_or_state": "Western Cape",
        "postal_or_zip_code": "8001",
        "country": "ZA"
      }
    },
    "metadata": {
      "custom_field_1": "custom data 1",
      "custom_field_2": "custom data 2"
    }
  },

  // Basket built from catalogue products — discount per line optional
  "items": [
    {
      "product_id": "pro_abc123...",
      "qty": 2,
      "discount": 100 // discount amount for this item (advanced use)
    },
    {
      "product_id": "pro_xyz789...",
      "qty": 3
    },

     // ...when no product_id is passed a product can be created
    {
      "name": "Generic Wooden Ball",
      "description": "Good For Training And Recreational Purposes",
      "code": "PRD-606075",
      "unit_label": "each",
      "price_excl": "2409.86",
      "is_tax": false,
      "pricing_type": "RECURRING",
      "product_status": "ACTIVE",
      "billing_period": "MONTHLY",
      "custom_period": null,
      "custom_interval": null,
      "picture_default": null,
      "pictures": {} // {...} media bundle when uploading gallery assets programmatically
    }
  ],

  "invoice": {
    "is_generate": true,
    "is_send": true
  },

  // Restrict rails shown — omit array to expose every method toggled on for the merchant
  "payment": {
    "payment_methods": ["CARD", "ZAPPER"]
  },

  "redirects": {
    // Successful redirect URLs may append `signature` when signatures are configured.
    "success_url": "https://merchant.example/success?myquery=myparam",
    "cancel_url": "https://merchant.example/cancel"
  },

  "settings": {
    "expiry_time": 1440 // minutes until checkout link lapses — omit when the link never expires (default)
  },

  "customization": {
    "button_text": "Pay",
    "type": "PAGE", // or "EMBED" for iframe-style hosting or in-app payments
    "is_display_cancel_button": true,
    "is_display_total": true,
    "is_display_items": true, // cart line detail list
    "brand": {
      "primary": "#00DC82",
      "secondary": "#CCCCCC"
    }
  }
}

Example (Embed payments)

Embed payments into your mobile app or frameworks like React, Angular or Vue

{
  "type": "PRODUCTS_AND_SUBSCRIPTIONS",

  "customization": {
    "button_text": "Pay now", // [optional] button text — default label if omitted depends on checkout
    "type": "EMBED", // show embedded screen that can be displayed in a 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 'payment.cancelled' message
    "is_display_total": false, // hide the total amount
    "is_display_items": false, // hide the checkout items
    "brand": {
      "primary": "#00DC82", // brand according to your app
      "secondary": "#CCCCCC"
    }
  },

  "customer_id": "cus_abc123...", // existing customer record; alternatively pass full customer object

  // Catalog lines (one-off and recurring products may be mixed — see Products reference)
  "items": [
    {
      "product_id": "pro_abc123...",
      "qty": 2 // default 1 when omitted
    },
    {
      "product_id": "pro_xyz789..."
    }
  ],

  // Invoice for this checkout / subscriptions created from it
  "invoice": {
    "is_generate": true, // generate invoice for the basket or subscription billing
    "is_send": true // email invoice when paid
  },
}

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

  • payment.loaded
  • payment.cancelled
  • payment.failed
  • payment.successful
  • payment.pending
  • payment.closed

The following data will be sent in the message on payment.successful events to verify the transaction:

{
  "source": "kwik-payments",
  "event": "payment.successful",
  "sessionId": "ses_abc123...",
  "transactionId": "tra_abc123...",
  "customerId": "cus_abc123...",
  "status": "PAID"
}

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

{
  "source": "kwik-payments",
  "event": "payment.failed",
  "sessionId": "ses_abc123...",
  "transactionId": "tra_abc123...",
  "customerId": "cus_abc123...",
  "status": "DECLINED",
  "message": "Your transaction was declined 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 and whether you pass customer_id vs inline customer.

Field
RequiredType
Description
Example
typeYENUMCheckout mode: CHOOSE_WHAT_TO_PAY (flexible / donation-style) or PRODUCTS_AND_SUBSCRIPTIONS (catalog line items)CHOOSE_WHAT_TO_PAY
itemCObjectPresent when type is CHOOSE_WHAT_TO_PAYSee basic and donation examples
item.titleCStringProduct or payment title; shown on the checkout and invoiceMy product name
item.descriptionNStringOptional longer product descriptionMy product description
item.frequencyNArrayFrequencies the payer can choose (e.g. one-time and subscriptions). Defaults to ONE_TIME if omitted["ONE_TIME", "MONTHLY", "ANNUALLY"]
item.amountNStringPreset amount; if omitted the user can enter an amount1200.00
item.amount_optionsNArrayPreset amounts the user can pick from, max 5 amounts[50.00, 100.00, 150.00, 200.00, 250.00]
item.amount_maxNNumberMaximum amount allowed when the user enters an amount3000.00
itemsCArrayPresent when type is PRODUCTS_AND_SUBSCRIPTIONS; each row either references product_id or defines an inline product (omit product_id), see productsSee examples
items.product_idCStringCatalog product id when referencing an existing product; omit for an inline-defined line itempro_abc123...
items.qtyNIntegerQuantity for this line item (default 1)2
items.discountNNumberDiscount amount for catalog lines (product_id present), advanced use100.00
items.nameCStringProduct name when defining an inline item (no product_id)Generic Wooden Ball
items.descriptionNStringInline item descriptionGood For Training And Recreational Purposes
items.codeCStringUnique product code; record may be created when uniquePRD-606075
items.unit_labelNStringUnit labeleach
items.price_exclCStringPrice excluding tax2409.86
items.is_taxNBooleanWhether tax appliesfalse
items.pricing_typeCENUMe.g. RECURRING or one-time as per your catalogRECURRING
items.product_statusNENUMe.g. ACTIVEACTIVE
items.billing_periodCStringWhen recurring (e.g. MONTHLY)MONTHLY
items.custom_periodNnullCustom billing period detail if applicablenull
items.custom_intervalNnullCustom interval if applicablenull
items.picture_defaultNnullDefault image referencenull
items.picturesNObjectGallery or image payload{}
signatureNStringOptional passphrase signature for verifying the payloadsecret-key-for-payload
customer_idCStringUse an existing customer record instead of customer inline objectcus_abc123...
customer_fieldsNArrayExtra fields on the payer form when neither customer_id nor customer is supplied; name, surname and email always show["CONTACT_NUMBER", "ID_NUMBER", "COMPANY_NAME", "COMPANY_REG", "VAT_NUMBER", "BILLING_ADDRESS", "SHIPPING_ADDRESS"]
customerCObjectCreate or prefill payer details when not using only customer_idSee advanced example
customer.referenceCString(35)Your reference for this customerCLN240919000001
customer.company_nameCString(64)Company name when applicablenull
customer.person_nameCString(32)First nameJohn
customer.person_surnameCString(32)SurnameDoe
customer.client_typeNENUMRESIDENT_INDIVIDUAL, NON_RESIDENT_INDIVIDUAL, DOMESTIC_COMPANY, FOREIGN_COMPANY, FINANCIAL_INSTITUTIONRESIDENT_INDIVIDUAL
customer.id_typeNENUMSOUTH_AFRICAN_ID, PASSPORT_NUMBER, TEMPORARY_RESIDENCE, COMPANY_REGISTRATION_NUMBERSOUTH_AFRICAN_ID
customer.id_numberNString(13)Identity or registration number8007014800087
customer.emailCString(128)Email addressjohndoe@mail.com
customer.contact_numberCString(16)Contact number+27831234567
customer.billing_address.line1NStringBilling line 11 Loop Street
customer.billing_address.line2NStringBilling line 2Floor 5
customer.billing_address.city_or_townNStringCity or townCape Town
customer.billing_address.province_or_stateNStringProvince or stateWestern Cape
customer.billing_address.postal_or_zip_codeNStringPostal or ZIP code8001
customer.billing_address.countryNString(2)ISO 3166-1 alpha-2 country codeZA
customer.shipping.contact_nameNStringShipping contact nameJane Doe
customer.shipping.contact_numberNString(16)Shipping contact number+27820000000
customer.shipping.address.line1NStringShipping line 11 Loop Street
customer.shipping.address.line2NStringShipping line 2Floor 5
customer.shipping.address.city_or_townNStringShipping cityCape Town
customer.shipping.address.province_or_stateNStringShipping province or stateWestern Cape
customer.shipping.address.postal_or_zip_codeNStringShipping postal code8001
customer.shipping.address.countryNString(2)ISO 3166-1 alpha-2ZA
customer.metadataNObjectCustom key/value metadata{"custom_field_1": "custom data 1"}
invoice.is_generateCBooleanWhether to generate an invoice for the payment or subscriptiontrue
invoice.is_sendNBooleanWhether to send the paid invoice to the customertrue
notification.emailNStringEmail address to notify on each successful paymentme@my-email.co.za
notification.webhook_urlNStringWebhook URL for each successful paymenthttps://merchant.example/webhooks/payments
redirects.success_urlNStringRedirect after successful payment; &signature= appended if configuredhttps://merchant.example/success?myquery=myparam
redirects.cancel_urlNStringRedirect if the payer cancelshttps://merchant.example/cancel
payment.payment_methodsNArrayLimit methods shown (CARD, ZAPPER, SNAPSCAN, APPLE_PAY, GOOGLE_PAY, SCAN_TO_PAY, 1VOUCHER, SCODE, MONEY_BADGER)[CARD, ZAPPER]
settings.expiry_timeNIntegerLink lifetime in minutes; omit so the link does not expire (per advanced example comment)1440
customization.button_textNStringPrimary action button labelPay
customization.confirmation_messageNStringMessage after successful payment (donation example); otherwise use redirectsThank you for your donation
customization.typeNENUMCreate a link for a PAGE or pass through EMBED for iframe or in-app payments. 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.is_display_totalNBooleanShow totaltrue
customization.is_display_itemsNBooleanShow line itemstrue
customization.brand.primaryNStringPrimary hex colour#00DC82
customization.brand.secondaryNStringSecondary hex colour#CCCCCC

Response Body

{
    "status": true,
    "result": {
      "id": "chk_HVpCeoNys1f22X7QcuWHY",
      "session_id": "ses_G-xkVKoxHgEBrY8suKgR3",
      "amount": "1200.00",
      "currency": "ZAR",
      "customer_id": "cus_abc123...",
      "link_url": "https://pay.kwik.co.za/checkout/cs_test_a1b2c3",
      "expires_at": "2025-09-13T12:30:00Z"
    }
}

Response Parameters

Field
Type
Description
Example
statusBooleanIndicates if the request was successfultrue
result.idStringUnique checkout session identifierchk_HVpCeoNys1f22X7QcuWHY
result.session_idString (32)Session identifier for the checkoutses_G-xkVKoxHgEBrY8suKgR3
result.amountStringCheckout amount1200.00
result.currencyString(32)ISO 4217 currency codeZAR
result.customer_idString(32)Associated customer IDcus_abc123...
result.link_urlStringURL to redirect user to complete checkouthttps://pay.kwik.co.za/checkout/cs_test_a1b2c3
result.expires_atStringISO timestamp when the checkout session expires2025-09-13T12:30:00Z

Signature creation

When creating API keys on the dashboard you can download a passphrase key, use the key in the examples below to generate your signature and send it in the signature parameter:

const crypto = require('crypto')

function canonicalize(value) {
  if (value === null || typeof value !== 'object') {
    return JSON.stringify(value)
  }

  if (Array.isArray(value)) {
    return `[${value.map(canonicalize).join(',')}]`
  }

  return `{${Object.keys(value)
    .filter((key) => key !== 'signature' && value[key] !== undefined)
    .sort()
    .map((key) => `${JSON.stringify(key)}:${canonicalize(value[key])}`)
    .join(',')}}`
}

function generateKwikSignature(payload, passphrase) {
  const canonicalPayload = canonicalize(payload)

  return crypto
    .createHmac('sha256', passphrase)
    .update(canonicalPayload)
    .digest('hex')
}

// Usage example
const payload = {
  type: 'CHOOSE_WHAT_TO_PAY',
  item: {
    title: 'My product name',
    frequency: ['ONE_TIME'], // optional
    amount: '1200.00'       // optional
  }
}

// get this from the dashboard when creating an API key
const passphrase = 'your-secure-passphrase'

const signature = generateKwikSignature(payload, passphrase)

const requestBody = {
  ...payload,
  signature
}

console.log('Canonical payload:', canonicalize(payload))
console.log('Signature:', signature)
console.log('Request body:', requestBody)

Webhook

When a checkout session is completed, updated, or expires, 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 checkout.completed, checkout.expired, checkout.failed, and others listed under Webhook events. The subscription object is present only when a recurring collection is created.

Webhook Payload

{
  // checkout.completed · checkout.expired · checkout.failed · etc.
  "event": "checkout.completed",
  "data": [
    {
      "checkout": {
        "id": "chk_HVpCeoNys1f22X7QcuWHY",
        "session_id": "ses_G-xkVKoxHgEBrY8suKgR3",
        "amount": "1200.00",
        "currency": "ZAR",
        "customer_id": "cus_abc123...",
        "card_id": "crd_SFq2E9LskQimPkf2mRqnV", // populated for card tenders
        "transaction_id": "tra_pr6CvR_4pvWwmgQ4y3dtY",
        "transaction_status": "ACTIVE",
        "expires_at": "2025-09-13T12:30:00Z",
        "completed_at": "2025-09-13T10:15:30Z"
      },
      // Only present once a recurring collection / mandate results from checkout
      "subscription": {
        "id": "col_Pw5jImVFZfpd0Lplp35op",
        "amount": "1200.00",
        "currency": "ZAR",
        "subscription_status": "ACTIVE",
        "payment_method": "CARD",
        "payment_method_id": "pam_ak7lHmJ0a2fuD1q4M6b0q",
        "created_at": "2025-09-13T10:15:30Z"
      }
    }
  ],
  "created_at": "2025-09-13T12:30:00Z"
}

Webhook Payload Parameters

Field
Type
Description
Example
eventStringType of webhook event that occurredcheckout.completed
dataArrayArray containing checkout and payment data...
data.checkout.idStringUnique checkout session identifierchk_HVpCeoNys1f22X7QcuWHY
data.checkout.session_idStringSession identifier for the checkoutses_G-xkVKoxHgEBrY8suKgR3
data.checkout.amountStringCheckout amount1200.00
data.checkout.currencyString(3)ISO 4217 currency codeZAR
data.checkout.customer_idString(32)Associated customer IDcus_abc123...
data.checkout.card_idStringCard identifier when payment was made with a saved or newly created cardcrd_SFq2E9LskQimPkf2mRqnV
data.checkout.transaction_idStringTransaction identifiertra_pr6CvR_4pvWwmgQ4y3dtY
data.checkout.transaction_statusENUMTransaction status, see lookupsACTIVE
data.checkout.expires_atStringISO timestamp when checkout session expires2025-09-13T12:30:00Z
data.checkout.completed_atStringISO timestamp when checkout was completed2025-09-13T10:15:30Z
data.subscription.idStringSubscription identifiercol_Pw5jImVFZfpd0Lplp35op
data.subscription.amountStringSubscription amount1200.00
data.subscription.currencyString(3)ISO 4217 currency codeZAR
data.subscription.subscription_statusENUMSubscription status, see lookupsACTIVE
data.subscription.payment_methodStringPayment method usedCARD
data.subscription.payment_method_idStringPayment method id usedpam_ak7lHmJ0a2fuD1q4M6b0q
data.subscription.created_atStringISO timestamp when payment was created2025-09-13T10:15:30Z
created_atStringISO timestamp when webhook was created2025-09-13T10:15:30Z

Webhook Events

EventDescriptionTrigger ConditionData Included
checkout.completedCheckout session was successfully completed with paymentWhen customer completes payment successfullyCheckout details, payment information, transaction data
checkout.expiredCheckout session expired without completionWhen checkout session reaches expiry time without paymentCheckout details only, no payment data
checkout.failedCheckout session failed due to payment failureWhen payment processing fails (declined card, insufficient funds, etc.)Checkout details, failed payment attempt, error information
checkout.abandonedCustomer abandoned the checkout processWhen customer leaves checkout page without completingCheckout details, abandonment timestamp
checkout.pendingPayment is pending additional verificationWhen payment requires manual review or 3DS authenticationCheckout details, pending payment status
checkout.cancelledCheckout was cancelled by customer or systemWhen cancel button is used or system cancels due to fraudCheckout details, cancellation reason

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.