Money In

Checkout Form

 

Create a secure, customizable checkout form 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

Path

POST /checkout/form

Example (Basic)

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

<form action="[path]/checkout/form" method="post">
  <input type="hidden" name="public_key" value="<INSERT YOUR PUBLIC KEY>">
  <input type="hidden" name="type" value="CHOOSE_WHAT_TO_PAY">
  <!-- item: title on checkout / invoice -->
  <input type="hidden" name="item_title" value="My product name">
  <!-- [optional] default ONE_TIME when item_frequency omitted -->
  <input type="hidden" name="item_frequency" value="ONE_TIME">
  <!-- [optional] preset an amount to pay else an amount field will show for the user to enter -->
  <input type="hidden" name="item_amount" value="1200.00">

  <input type="submit" value="Pay">
</form>

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.

<form action="[path]/checkout/form" method="post">
  <input type="hidden" name="public_key" value="<INSERT YOUR PUBLIC KEY>">

  <input type="hidden" name="type" value="CHOOSE_WHAT_TO_PAY">

  <!-- Pass your product / campaign details via item -->
  <input type="hidden" name="item_title" value="My product name">
  <input type="hidden" name="item_description" value="My product description">
  <!-- [optional] options the payer may choose among -->
  <input type="hidden" name="item_frequency" value="ONE_TIME,MONTHLY,ANNUALLY">
  <!-- [optional] preset amounts (max five) -->
  <input type="hidden" name="item_amount_options" value="50,100,150,200,250">
  <!-- [optional] cap when payer enters any amount -->
  <input type="hidden" name="item_amount_max" value="3000">

  <input type="hidden" name="customization_button_text" value="Donate now">
  <input type="hidden" name="customization_confirmation_message" value="Thank you for your donation">

  <!-- [optional] when API keys/signing are configured -->
  <input type="hidden" name="signature" value="secret-key-for-payload">

  <input type="hidden" name="notification_email" value="me@my-email.co.za">
  <input type="hidden" name="notification_webhook_url" value="https://merchant.example/webhooks/payments">

  <input type="hidden" name="invoice_is_generate" value="true">
  <input type="hidden" name="invoice_is_send" value="true">

  <!-- Comma-separated enums when no customer_id / prefilled customer (name, surname, email always captured) -->
  <input type="hidden" name="customer_fields" value="CONTACT_NUMBER,ID_NUMBER,COMPANY_NAME,COMPANY_REG,VAT_NUMBER,BILLING_ADDRESS,SHIPPING_ADDRESS">

  <input type="submit" value="Donate">
</form>

Example (Products, subscriptions and invoices)

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

<form action="[path]/checkout/form" method="post">
  <input type="hidden" name="public_key" value="<INSERT YOUR PUBLIC KEY>">

  <input type="hidden" name="type" value="PRODUCTS_AND_SUBSCRIPTIONS">

  <input type="hidden" name="customization_button_text" value="Pay now">

  <!-- [optional] payload signature when configured -->
  <input type="hidden" name="signature" value="secret-key-for-payload">

  <input type="hidden" name="customer_id" value="cus_abc123...">

  <input type="hidden" name="items_0_product_id" value="pro_abc123...">
  <input type="hidden" name="items_0_qty" value="2">
  <input type="hidden" name="items_1_product_id" value="pro_xyz789...">
  <!-- items_1_qty omitted → default qty 1 -->

  <input type="hidden" name="invoice_is_generate" value="true">
  <input type="hidden" name="invoice_is_send" value="true">

  <input type="hidden" name="redirects_success_url" value="https://merchant.example/success?myquery=myparam">
  <input type="hidden" name="redirects_cancel_url" value="https://merchant.example/cancel">

  <input type="submit" value="Pay">
</form>

Example (Advanced )

Advanced example with example with all options

<form action="[path]/checkout/form" method="post">
  <input type="hidden" name="public_key" value="<INSERT YOUR PUBLIC KEY>">

  <input type="hidden" name="type" value="PRODUCTS_AND_SUBSCRIPTIONS">

  <!-- [optional] shared-secret signature for the payload, see the signature section below -->
  <input type="hidden" name="signature" value="secret-key-for-payload">

  <input type="hidden" name="customer_reference" value="CLN240919000001">
  <input type="hidden" name="customer_company_name" value="">
  <input type="hidden" name="customer_person_name" value="John">
  <input type="hidden" name="customer_person_surname" value="Doe">
  <input type="hidden" name="customer_client_type" value="RESIDENT_INDIVIDUAL">
  <input type="hidden" name="customer_id_type" value="SOUTH_AFRICAN_ID">
  <input type="hidden" name="customer_id_number" value="8007014800087">
  <input type="hidden" name="customer_email" value="johndoe@mail.com">
  <input type="hidden" name="customer_contact_number" value="+27831234567">
  <input type="hidden" name="customer_billing_address_line1" value="1 Loop Street">
  <input type="hidden" name="customer_billing_address_line2" value="Floor 5">
  <input type="hidden" name="customer_billing_address_city_or_town" value="Cape Town">
  <input type="hidden" name="customer_billing_address_province_or_state" value="Western Cape">
  <input type="hidden" name="customer_billing_address_postal_or_zip_code" value="8001">
  <input type="hidden" name="customer_billing_address_country" value="ZA">
  <input type="hidden" name="customer_shipping_contact_name" value="Jane Doe">
  <input type="hidden" name="customer_shipping_contact_number" value="+27820000000">
  <input type="hidden" name="customer_shipping_address_line1" value="1 Loop Street">
  <input type="hidden" name="customer_shipping_address_line2" value="Floor 5">
  <input type="hidden" name="customer_shipping_address_city_or_town" value="Cape Town">
  <input type="hidden" name="customer_shipping_address_province_or_state" value="Western Cape">
  <input type="hidden" name="customer_shipping_address_postal_or_zip_code" value="8001">
  <input type="hidden" name="customer_shipping_address_country" value="ZA">
  <input type="hidden" name="customer_metadata_custom_field_1" value="custom data 1">
  <input type="hidden" name="customer_metadata_custom_field_2" value="custom data 2">

  <!-- catalogue lines -->
  <input type="hidden" name="items_0_product_id" value="pro_abc123...">
  <input type="hidden" name="items_0_qty" value="2">
  <input type="hidden" name="items_0_discount" value="100">
  <input type="hidden" name="items_1_product_id" value="pro_xyz789...">
  <input type="hidden" name="items_1_qty" value="3">

  <!-- inline product row (omit items_2_product_id) -->
  <input type="hidden" name="items_2_name" value="Generic Wooden Ball">
  <input type="hidden" name="items_2_description" value="Good For Training And Recreational Purposes">
  <input type="hidden" name="items_2_code" value="PRD-606075">
  <input type="hidden" name="items_2_unit_label" value="each">
  <input type="hidden" name="items_2_price_excl" value="2409.86">
  <input type="hidden" name="items_2_is_tax" value="false">
  <input type="hidden" name="items_2_pricing_type" value="RECURRING">
  <input type="hidden" name="items_2_product_status" value="ACTIVE">
  <input type="hidden" name="items_2_billing_period" value="MONTHLY">
  <input type="hidden" name="items_2_custom_period" value="">
  <input type="hidden" name="items_2_custom_interval" value="">
  <input type="hidden" name="items_2_picture_default" value="">

  <input type="hidden" name="invoice_is_generate" value="true">
  <input type="hidden" name="invoice_is_send" value="true">

  <!-- comma-separated enums: CARD,ZAPPER,... -->
  <input type="hidden" name="payment_methods" value="CARD,ZAPPER">

  <!-- Successful redirect URLs may append `signature` when signatures are configured. -->
  <input type="hidden" name="redirects_success_url" value="https://merchant.example/success?myquery=myparam">
  <input type="hidden" name="redirects_cancel_url" value="https://merchant.example/cancel">

  <input type="hidden" name="settings_expiry_time" value="1440">

  <input type="hidden" name="customization_button_text" value="Pay">
  <!-- page-style checkout only; embed / iframe payloads are documented on Checkout Link, not duplicated here -->
  <input type="hidden" name="customization_is_display_cancel_button" value="true">
  <input type="hidden" name="customization_is_display_total" value="true">
  <input type="hidden" name="customization_is_display_items" value="true">
  <input type="hidden" name="customization_brand_primary" value="#00DC82">
  <input type="hidden" name="customization_brand_secondary" value="#CCCCCC">

  <input type="submit" value="Pay">
</form>

Request Parameters

Form input names mirror the Checkout Link request parameters. Y applies to forms that POST a checkout; C depends on type and whether you send customer_id vs prefixed customer_* fields.

Field (input name)
RequiredType
Description
Example
public_keyYStringPublic API key (dashboard / integrations). Always include.<INSERT YOUR PUBLIC KEY>
typeYENUMSame as Checkout Link: CHOOSE_WHAT_TO_PAY or PRODUCTS_AND_SUBSCRIPTIONS.CHOOSE_WHAT_TO_PAY
item_titleCStringFlexible checkout title (invoice / UI).My product name
item_descriptionNStringFlexible checkout description.My product description
item_frequencyNStringComma-separated frequency enums (ONE_TIME, MONTHLY, …). Default is ONE_TIME.ONE_TIME,MONTHLY,ANNUALLY
item_amountNStringPreset amount; omit so payer enters amount.1200.00
item_amount_optionsNStringComma-separated preset amounts (max five values, matches Checkout Link).50,100,150,200,250
item_amount_maxNNumberCap when payer enters any amount.3000
signatureNStringPayload signature when your integration signs requests.secret-key-for-payload
items__product_idCStringnth line item catalogue id (n zero-based). Omit for an inline product row on that index. See products.pro_abc123...
items__qtyNIntegerQuantity for nth line (default 1 when omitted).2
items__discountNNumberDiscount for nth catalogue line (product_id present).100
items__nameCStringInline line only: product name when items_{n}_product_id is absent.Generic Wooden Ball
items__descriptionNStringInline line description.Good For Training …
items__codeCStringInline line unique code.PRD-606075
items__unit_labelNStringInline unit label.each
items__price_exclCStringInline price excluding tax.2409.86
items__is_taxNBooleanInline tax flag.false
items__pricing_typeCENUMInline pricing type (RECURRING, etc.).RECURRING
items__product_statusNENUMe.g. ACTIVE.ACTIVE
items__billing_periodCStringWhen recurring.MONTHLY
items__custom_periodNStringOptional opaque period value.
items__custom_intervalNStringOptional opaque interval value.
items__picture_defaultNStringOptional default picture reference.
customer_idCStringExisting customer instead of prefixed customer_* body.cus_abc123...
customer_fieldsNStringComma-separated field tokens when payer form should collect extras (LINK enum list — see Checkout Link). Uppercase enums as in JSON.CONTACT_NUMBER,ID_NUMBER,…
customer_reference …CPrefixSame nested customer fields as Checkout Link flattened: customer_person_name, customer_billing_address_line1, customer_metadata_{key} (underscore key segments), etc.See advanced HTML example
invoice_is_generateCBooleanGenerate invoice (true / false).true
invoice_is_sendNBooleanSend paid invoice (true / false).true
notification_emailNStringNotify email on successful payment.me@my-email.co.za
notification_webhook_urlNStringWebhook URL for successful payment notifications (same semantics as Checkout Link notification.webhook_url).https://merchant.example/webhooks/payments
redirects_success_urlNStringSuccess redirect (redirects.success_url in JSON).https://merchant.example/success
redirects_cancel_urlNStringCancel redirect (redirects.cancel_url in JSON).https://merchant.example/cancel
payment_methodsNStringComma-separated method codes (CARD, ZAPPER, …) — mirrors payment.payment_methods JSON array.CARD,ZAPPER
settings_expiry_timeNIntegerMinutes until checkout link/session expiry (settings.expiry_time).1440
customization_button_textNStringPrimary button label.Pay
customization_confirmation_messageNStringMessage after payment (flexible flows).Thank you…
customization_is_display_cancel_buttonNBooleanShow cancel control.true
customization_is_display_totalNBooleanShow total.true
customization_is_display_itemsNBooleanShow line items.true
customization_brand_primaryNStringPrimary hex colour.#00DC82
customization_brand_secondaryNStringSecondary hex colour.#CCCCCC

For full JSON-shape definitions (enums, array lengths, behavioural notes), use Checkout Link alongside this flattened mapping.

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 express = require('express')
const crypto = require('crypto')
const app = express()

function generateSignature(params, passphrase) {
  const canonical = Object.keys(params)
    .sort()
    .map(k => `${k}=${params[k]}`)
    .join('&')

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

app.get('/checkout', (req, res) => {
  const params = {
    public_key: 'pk_test_123',
    type: 'CHOOSE_WHAT_TO_PAY',
    item_title: 'My product name',
    item_frequency: 'ONE_TIME',
    item_amount: '1200.00'
  }

  const signature = generateSignature(params, 'your-passphrase')

  res.send(`
    <form action="https://api.kwik.co.za/checkout/form" method="post">
      ${Object.entries(params).map(([k,v]) => `<input type="hidden" name="${k}" value="${v}">`).join('')}
      <input type="hidden" name="signature" value="${signature}">
      <button type="submit">Pay</button>
    </form>
  `)
})

app.listen(3000)

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.