CentraPoint

Guides

Hosted checkout & checkout sessions

Sell subscription packages and products on branded hosted checkout pages, and create pre-filled checkout links for known customers with the checkout sessions API.

On this page

Overview#

Hosted checkout pages are reusable, branded pages on CentraPoint where anyone with the link can subscribe to one of your subscription packages or buy one of your products. The page collects the payer's details, applies coupons and tax, shows an order summary and hands the payer to your payment gateway. Afterwards CentraPoint shows your success or failure page and, if you asked for it, sends the payer back to your site.

There are two ways to use them:

  • Share the page link (or paste the button HTML into your website). Anyone can open it and check out.
  • Create a checkout session from your own system with POST /api/v1/checkout-sessions. You get a one-off link for 24 hours, pre-filled with the customer's details, a plan and a coupon, and a return URL on your site. See Checkout sessions.

Both routes record a checkout session, so the status, the resulting payment or subscription and the checkout webhooks work the same way.

Requirements#

What your plan needs
ToYour plan needs
Sell subscription packages (package checkout)Recurring card billing
Sell products (product checkout)Payment links; recurring products also need Recurring card billing
Accept coupon codes at checkoutCoupons
Create checkout sessions through the APIREST API & API keys, plus the feature for what you sell
Receive checkout.completed / checkout.failedREST API & API keys (webhook endpoints)

You also need at least one enabled payment gateway that supports online checkout in the price's currency (Manual EFT and Netcash debit orders are not offered on hosted checkout). Without one, only free trials and free first charges can be completed.

The page is unavailable ("This merchant is not accepting online payments at the moment") while your account is suspended, cancelled or read-only. In the dashboard, viewing checkout pages needs the checkout_pages:read permission and editing them checkout_pages:write (see Roles).

Checkout page URLs#

Every enabled package and product gets a checkout page automatically. The URL uses your organisation's slug and the package or product ID:

Hosted checkout URLs
PageURL
Package (subscription) checkouthttps://app.centrapoint.co.za/c/{tenantSlug}/package/{packageId}
Package with a plan pre-selected…/c/{tenantSlug}/package/{packageId}?plan={planId}
Product checkouthttps://app.centrapoint.co.za/c/{tenantSlug}/product/{productId}
Checkout session (from the API)…/package/{packageId}?session={token} or …/product/{productId}?session={token}
Success page…/c/{tenantSlug}/success?ref={reference}
Failure page…/c/{tenantSlug}/failure?ref={reference}

Find the links under Settings → Checkout pages (Copy link, Copy button HTML and Open for each package and product) and in the Share checkout card on the Payment links page. The button HTML is a styled <a> link ("Subscribe" for packages, "Buy now" for products) in your brand colour.

A package page needs the package enabled with at least one enabled plan; a product page needs the product enabled. Otherwise the page says the item is not available for online checkout. Checkout pages are not indexed by search engines.

Designing checkout pages#

Go to Settings → Checkout pages. There are three pages per checkout: the Checkout page, the Success page and the Failure page. The first row, All checkouts, is your default design; each package and product row can have its own design for any of the three pages. The design used is the package or product's own design, else your default, else the built-in design. Each cell shows Customised, Uses default or Built-in.

The editor shows a live preview (desktop or mobile width) of your unsaved changes; the default design is previewed on your first enabled package or product. Payments and coupon checks are disabled in the preview. Use default design removes a package or product's own design; Reset to built-in removes your default design. The logo and brand colour come from Settings → Branding; the terms and privacy links from Settings → Email.

Page options#

Checkout page designer options
FieldTypeDescription
Templaterequiredall pagesClassic (single column, order summary above the form), Split (summary beside the form on wide screens) or Minimal (compact, borderless).
Headlineoptionalall pagesUp to 120 characters. Checkout: blank = the package or product name. Success default: "Payment successful"; failure default: "Payment not completed".
Subheadlineoptionalall pagesUp to 300 characters. Checkout: blank = the package or product description.
Accent colouroptionalall pagesA hex colour such as #0b5f7f; off = your brand colour.
Show logooptionalall pagesShows your branding logo (otherwise your company name).
Footer textoptionalall pagesUp to 500 characters under the page.
Customer fieldsoptionalcheckoutRequired, optional or hidden per field (below).
Pay button textoptionalcheckoutUp to 40 characters. Blank = "Pay R 199,00" (the amount due), "Start free trial" or "Continue".
Trust badgesoptionalcheckoutUp to 5 lines of up to 80 characters shown under the pay button. Default: "Secure checkout" and "Your card details are handled by the payment provider".
Success / failure messageoptionalsuccess, failureUp to 2000 characters. A default message is used when blank.
Redirect URLoptionalsuccess, failureAn https URL the payer can continue to. A checkout session's returnUrl takes precedence.
Button textoptionalsuccess, failureUp to 40 characters, for the redirect button. Defaults: "Continue" / "Back to the website".
Auto-redirect after (seconds)optionalsuccess, failure0–60; 0 = only when clicked. Default 8 on the success page and 0 on the failure page.
Retry button textoptionalfailureUp to 40 characters, default "Try again".

Customer fields#

Email address and first name are always required; last name is always shown and optional. The other fields can each be set to required, optional or hidden:

Configurable customer fields
FieldDefaultLimit
Phone numberOptional30 characters
Company nameOptional200 characters
Billing addressHidden1000 characters
VAT numberHidden30 characters

The rules are enforced when the payer submits: a required field that is empty is rejected, and values for hidden fields are ignored.

What the payer sees#

Every checkout page has, in order:

  1. Your logo (or company name), the headline and subheadline.
  2. The plan picker (package checkout only) and the order summary.
  3. Your details: email, first name, last name and the fields you enabled. On a checkout session with an email address, the email is locked ("This checkout was prepared for this email address").
  4. Coupon code (only when your plan includes Coupons). Apply checks the code and updates the order summary; an invalid code shows a reason such as "This coupon has expired". A coupon from a checkout session is applied when the page opens and the payer can remove it. See Coupons.
  5. Pay with: a choice of payment gateway, shown only when more than one gateway can take the payment.
  6. A terms checkbox ("I accept the terms and conditions", plus the privacy policy link if set), shown when you set a terms URL under Settings → Email. The payer must tick it.
  7. The pay button, your trust badges, footer text and terms/privacy links.

The order summary shows the price, any setup fee, the discount with its coupon code, tax, the total due now and, for recurring items, what is charged later (for example "then R 499,00 per month").

When the payer clicks pay, CentraPoint redirects them (or posts a form) to the gateway. Mobile money gateways that approve the payment on the payer's phone show a waiting message with an I have approved the payment button instead. Submissions are rate-limited to 15 per IP address per 10 minutes and 10 per email address per hour; coupon checks to 20 per IP address per 10 minutes.

Package checkout#

  • The payer chooses one of the package's enabled plans (in sort order). Each plan card shows the price per period, the description and features, and badges for a free trial, a setup fee and a fixed number of payments. The first plan (or the plan from ?plan= or the checkout session) is selected; the payer can pick another.
  • Plans set up with a call-to-action link or contact us are shown with a Learn more / Contact us button (or your company email) and can't be bought on the page.
  • The gateways offered are the plan's allowed payment providers (all checkout gateways for the currency when none are set); the package's default provider is pre-selected.
  • The first payment is the setup fee plus the first period, less any coupon discount. When your prices exclude tax, tax is added on top of that (and later charges include tax too); when they include tax, the included tax is shown; with tax off, the price is charged as it is.
  • Free trial: when the plan has trial days, nothing is charged now. The button reads "Start free trial", the subscription starts in trialing and the payer goes straight to the success page ("Your free trial has started"). The same applies when the first charge works out to zero.

See the packages, plans & customer subscriptions guide for how the subscription is billed after the first payment.

Product checkout#

  • The price is the product amount less any coupon discount; tax follows your tax settings for taxable products (inclusive prices contain the tax, exclusive prices get it added).
  • The order total must be more than zero.
  • Recurring products are billed by the gateway at a fixed amount, so only gateways that support recurring billing (PayFast, Netcash Pay Now) are offered, and only "every charge" coupons can be used.
  • A new payment counts towards your plan's monthly transaction limit; at the limit the payer is asked to contact you.

What gets created#

When the payer submits the form:

  1. A checkout session is recorded (or the API session is used), and the customer is found or created (below).
  2. New customers get your welcome email, which includes a customer portal link when the customer portal is enabled.
  3. Package checkout: a customer subscription is created with source: "checkout", collection method gateway, the chosen gateway and coupon, and the checkout session's metadata. Unless it is a trial or free, a pending first payment is created (its internal metadata also holds the session's metadata) and the payer is sent to the gateway; the subscription becomes active when that payment completes.
  4. Product checkout: a pending payment (transaction) is created with the product, the customer, the discount and tax amounts, and in its internal metadata the checkout session's metadata keys plus checkoutSessionId (and couponCode), which win on a clash. The payer is sent to the gateway.
  5. A coupon use is recorded as a pending redemption until the payment settles.

When the payment completes, the session becomes completed, checkout.completed is sent and the payer gets the payment confirmation email; the success page offers the receipt PDF. The usual payment.complete and subscription webhooks are sent as well. If the gateway can't start the payment, the payer sees an error ("We could not start the payment. Please try again or choose another payment method.") and the coupon redemption is released.

Customer matching#

  • The payer is matched to your customers by email address (case-insensitive); with several matches, the oldest record is used.
  • An existing customer's details are never overwritten: only blank fields (last name, phone, company, address, VAT number) are filled in from the form.
  • An inactive customer can't check out ("Please contact … to complete this purchase.").
  • A new customer gets the next account code, is a company contact when a company name was given, and has the note "Created by hosted checkout". If your plan's customer limit is reached, the payer is asked to contact you.

Success and failure pages#

After the gateway, the payer comes back to /c/{tenantSlug}/success or /failure. The page always reflects the verified payment status, never the query string: a payment still pending shows "Confirming your payment…" and refreshes while CentraPoint checks the gateway, and a payer on the wrong page is moved to the right one.

  • The success page shows what was bought, the amount, any discount, the reference, when it was paid, the plan (and trial end date) and a Download receipt (PDF) link.
  • The failure page offers a retry button. For an API checkout session that has not expired, it reopens the same session link; otherwise it opens the checkout page again.

Returning to your site#

With a checkout session returnUrl, both pages show a button (your button text, default "Continue") that takes the payer to it automatically after your auto-redirect delay (at least 3 seconds; 5 when set to 0). CentraPoint adds:

Query parameters added to returnUrl
ParameterValue
statuscomplete on the success page; failed or cancelled on the failure page
referenceThe payment reference (not added when nothing was charged, e.g. a free trial)
sessionThe checkout session ID

Without a returnUrl, the page's own Redirect URL (if set) is used with its own delay; otherwise the payer stays on the page.

Checkout sessions#

A checkout session is a single checkout of a package or product. Your system creates one when it already knows the customer (for example after sign-up in your app), sends the payer to its checkoutUrl and gets the result back through the return URL, the API and webhooks. Sessions are also recorded for every direct visit to a checkout page (source: "hosted"), so you can list those too.

Checkout session endpoints
EndpointPurpose
POST /api/v1/checkout-sessionsCreate a pre-filled checkout link
GET /api/v1/checkout-sessionsList sessions
GET /api/v1/checkout-sessions/{id}Get a session with its payment and subscription
POST /api/billing-webhook/checkoutCreate a session (ASAP API shape)

Session status#

Checkout session statuses
StatusMeaning
pendingCreated, or the payer has submitted and the payment is not final yet.
completedThe payment completed, or a trial / free subscription was started. Final. A payment that completes after the session expired or failed still moves it here: the payment wins.
failedThe payment failed or was cancelled. The payer can retry on the same link until it expires; a successful retry moves it to completed.
expired24 hours passed without a completed payment (including a payment still in progress, or a failure without a successful retry). The link no longer works; create a new session. Only a late completed payment changes it (to completed).

Sessions expire 24 hours after creation. An expired or completed link shows "This checkout link has expired or was already used". Status only follows the verified payment, never the return URL.

Create a checkout session#

POST/api/v1/checkout-sessions
Create fields
FieldTypeDescription
packageIdoptionalstringPackage to sell. Must be enabled. Needs Recurring card billing.
planIdoptionalstringPlan to pre-select (enabled, not a call-to-action or contact-us plan). The package is taken from the plan when packageId is omitted; with both, the plan must belong to the package. The payer can still choose another plan.
productIdoptionalstringProduct to sell instead. Must be enabled. Needs Payment links. Send either packageId/planId or productId.
returnUrloptionalstringhttps URL (up to 1000 characters, #fragment dropped) on an origin allow-listed under Settings → API keys → Allowed return URLs. See Returning to your site.
customeroptionalobjectDetails to pre-fill: email (max 200), firstName, lastName (max 100), phone (max 30), company (max 200; companyName is accepted too). Values are trimmed and stored up to 100 characters (email 200); an email must be valid and locks the email field on the page. Other keys are ignored.
clientoptionalobjectAlias of customer; customer wins when both are sent.
couponCodeoptionalstringUp to 50 characters. Must be an existing, enabled coupon and your plan must include Coupons. It is applied when the page opens; its other rules are checked then.
metadataoptionalobjectKey/value data (rules), returned on the session and its webhooks, copied to the customer subscription of a package checkout, and stored with the payment it creates.
curl -X POST "https://app.centrapoint.co.za/api/v1/checkout-sessions" \
  -H "Authorization: Bearer $CENTRAPOINT_API_KEY" \
  -H "Idempotency-Key: 3b9f2c6e-1a4d-4e7b-8c2f-5d6a7b8c9e01" \
  -H "Content-Type: application/json" \
  -d '{
    "packageId": "cmg4p1a2b0001pkg0001abcd",
    "planId": "cmg4p1a2b0002pln0001abcd",
    "returnUrl": "https://www.acme.co.za/billing/done",
    "customer": {
      "email": "[email protected]",
      "firstName": "Thandi",
      "lastName": "Nkosi",
      "company": "Nkosi Design"
    },
    "couponCode": "WELCOME10",
    "metadata": {
      "crmDealId": "D-778"
    }
  }'

Returns 201 with the session object plus sessionId (the same as id). checkoutUrl is the link to send the payer to and expiresAt is 24 hours from now. Supports the Idempotency-Key header.

201 response (abridged)
{
  "id": "cmg6k2s9e0001cks0001abcd",
  "object": "checkout_session",
  "status": "pending",
  "source": "api",
  "checkoutUrl": "https://app.centrapoint.co.za/c/acme/package/cmg4p1a2b0001pkg0001abcd?session=q3Jx8Vn2cLr0TzP5mWk7yHs1aBd4eFg6hIj9kLm0nOp",
  "packageId": "cmg4p1a2b0001pkg0001abcd",
  "planId": "cmg4p1a2b0002pln0001abcd",
  "productId": null,
  "customerId": null,
  "subscriptionId": null,
  "couponCode": "WELCOME10",
  "…": "…",
  "transaction": null,
  "expiresAt": "2026-09-29T08:10:00.000Z",
  "usedAt": null,
  "sessionId": "cmg6k2s9e0001cks0001abcd"
}

The checkout session object#

Checkout session (completed)
{
  "id": "cmg6k2s9e0001cks0001abcd",
  "object": "checkout_session",
  "status": "completed",
  "source": "api",
  "checkoutUrl": null,
  "packageId": "cmg4p1a2b0001pkg0001abcd",
  "planId": "cmg4p1a2b0002pln0001abcd",
  "productId": null,
  "customerId": "cmg2c0s7t0003cust0001abcd",
  "subscriptionId": "cmg4s9d8e0005csb0001abcd",
  "couponCode": "WELCOME10",
  "returnUrl": "https://www.acme.co.za/billing/done",
  "prefill": {
    "email": "[email protected]",
    "firstName": "Thandi",
    "lastName": "Nkosi",
    "company": "Nkosi Design"
  },
  "metadata": {
    "crmDealId": "D-778"
  },
  "transaction": {
    "id": "cmg6t1x2n0006txn0001abcd",
    "reference": "CP-20260928-4B7E2A91",
    "status": "complete",
    "amount": 449.1,
    "currency": "ZAR",
    "discountAmount": 49.9,
    "taxAmount": 58.58,
    "paidAt": "2026-09-28T08:14:03.512Z"
  },
  "expiresAt": "2026-09-29T08:10:00.000Z",
  "usedAt": "2026-09-28T08:12:41.000Z",
  "createdAt": "2026-09-28T08:10:00.000Z",
  "updatedAt": "2026-09-28T08:14:04.020Z"
}
Checkout session fields
FieldTypeDescription
idrequiredstringSession ID.
objectrequiredstringcheckout_session
statusrequiredstringpending, completed, failed or expired (a session that is not completed is returned as expired once expiresAt passes).
sourcerequiredstringapi (created through the API) or hosted (a direct visit to a checkout page).
checkoutUrlrequiredstring | nullThe link while the session is usable (pending or failed, not expired); null otherwise and always null for hosted sessions.
packageId, planId, productIdrequiredstring | nullWhat is being sold. planId is updated to the plan the payer chose.
customerIdrequiredstring | nullSet once the payer submits the form.
subscriptionIdrequiredstring | nullThe customer subscription started by a package checkout.
couponCoderequiredstring | nullThe coupon set when the session was created.
returnUrlrequiredstring | null
prefillrequiredobjectThe stored email, firstName, lastName, phone, company (only those set).
metadatarequiredobjectYour key/value data ({} when none).
transactionrequiredobject | nullThe payment: id, reference, status, amount, currency, discountAmount, taxAmount (null when none), paidAt. Null until a payment is started (and for trials).
expiresAtrequiredstring24 hours after creation.
usedAtrequiredstring | nullWhen the payer first submitted the form.
createdAt, updatedAtrequiredstring

List checkout sessions#

GET/api/v1/checkout-sessions
List query parameters
FieldTypeDescription
statusoptionalstringpending, completed, failed or expired; anything else returns 400. Filters on the reported status: expired also matches pending and failed sessions past expiresAt that the daily housekeeping job (01:00 South African time) has not updated yet, and pending / failed only match sessions that have not expired.
customerIdoptionalstringSessions of this customer.
limitoptionalinteger1–100, default 20.

Returns { "data": [ … ] }, newest first, including hosted sessions.

curl -X GET "https://app.centrapoint.co.za/api/v1/checkout-sessions?status=completed&limit=50" \
  -H "Authorization: Bearer $CENTRAPOINT_API_KEY"

Get a checkout session#

GET/api/v1/checkout-sessions/{id}
curl -X GET "https://app.centrapoint.co.za/api/v1/checkout-sessions/cmg6k2s9e0001cks0001abcd" \
  -H "Authorization: Bearer $CENTRAPOINT_API_KEY"

Returns the session object, or 404 if it isn't yours.

ASAP compatibility endpoint#

POST/api/billing-webhook/checkout

For integrations built against the ASAP billing API. It creates the same checkout session, with the same authentication, rules and errors, but takes the ASAP field names and returns a short response.

Compatibility fields
FieldTypeDescription
packageIdoptionalstringAs packageId above.
subscriptionIdoptionalstringThe plan ID (planId above).
productIdoptionalstringAs productId above.
returnUrloptionalstringAs above; the origin must be allow-listed.
clientoptionalobjectemail, firstName, lastName, phone, company or companyName.
couponCode, metadataoptionalstring, objectAs above.
curl -X POST "https://app.centrapoint.co.za/api/billing-webhook/checkout" \
  -H "Authorization: Bearer $CENTRAPOINT_API_KEY" \
  -H "Idempotency-Key: 3b9f2c6e-1a4d-4e7b-8c2f-5d6a7b8c9e01" \
  -H "Content-Type: application/json" \
  -d '{
    "packageId": "cmg4p1a2b0001pkg0001abcd",
    "subscriptionId": "cmg4p1a2b0002pln0001abcd",
    "returnUrl": "https://www.acme.co.za/billing/done",
    "client": {
      "email": "[email protected]",
      "firstName": "Thandi",
      "companyName": "Nkosi Design"
    }
  }'
201 response
{
  "checkoutUrl": "https://app.centrapoint.co.za/c/acme/package/cmg4p1a2b0001pkg0001abcd?session=q3Jx8Vn2cLr0TzP5mWk7yHs1aBd4eFg6hIj9kLm0nOp",
  "sessionId": "cmg6k2s9e0001cks0001abcd",
  "expiresAt": "2026-09-29T08:10:00.000Z"
}

Webhooks#

Subscribe your webhook endpoints to these events. data is the checkout session object (object: "checkout_session") at the time of sending. They are sent for API and hosted sessions alike.

Checkout events
EventSent when
checkout.completedThe session's payment completed, or a package checkout started a free trial or a subscription with nothing to pay now.
checkout.failedThe session's payment failed or was cancelled (including an abandoned payment cancelled later). If the payer then retries successfully, or the payment completes later, checkout.completed follows.

Each is sent once per status change. Nothing is sent when a session expires unused. The payment and subscription events (for example payment.complete, subscription.created) are sent as usual.

checkout.completed
{
  "id": "evt_7c2d9e4f1a3b5c6d7e8f9a0b",
  "type": "checkout.completed",
  "created": "2026-09-28T08:14:04.311Z",
  "data": {
    "id": "cmg6k2s9e0001cks0001abcd",
    "object": "checkout_session",
    "status": "completed",
    "source": "api",
    "checkoutUrl": null,
    "packageId": "cmg4p1a2b0001pkg0001abcd",
    "planId": "cmg4p1a2b0002pln0001abcd",
    "productId": null,
    "customerId": "cmg2c0s7t0003cust0001abcd",
    "subscriptionId": "cmg4s9d8e0005csb0001abcd",
    "couponCode": "WELCOME10",
    "returnUrl": "https://www.acme.co.za/billing/done",
    "prefill": {
      "email": "[email protected]",
      "firstName": "Thandi",
      "lastName": "Nkosi",
      "company": "Nkosi Design"
    },
    "metadata": {
      "crmDealId": "D-778"
    },
    "transaction": {
      "id": "cmg6t1x2n0006txn0001abcd",
      "reference": "CP-20260928-4B7E2A91",
      "status": "complete",
      "amount": 449.1,
      "currency": "ZAR",
      "discountAmount": 49.9,
      "taxAmount": 58.58,
      "paidAt": "2026-09-28T08:14:03.512Z"
    },
    "expiresAt": "2026-09-29T08:10:00.000Z",
    "usedAt": "2026-09-28T08:12:41.000Z",
    "createdAt": "2026-09-28T08:10:00.000Z",
    "updatedAt": "2026-09-28T08:14:04.020Z"
  }
}

Errors#

Checkout session errors
StatuserrorWhen
400invalid_requestBody validation failed (Invalid body with issues), or: "Send either packageId/planId or productId, not both", "packageId, planId or productId is required", "Package not found or disabled", "Plan not found or disabled", "This plan is not sold through checkout", "Product not found or disabled", a returnUrl error ("returnUrl is not a valid URL", "returnUrl must use https", "returnUrl is too long", "returnUrl origin … is not allowed - add it under Settings > API keys > Allowed return URLs"), "couponCode is not a valid, enabled coupon", "Coupons are not included in your plan", "client.email is not a valid email address", a metadata error, or an unknown status filter on the list.
403plan_restrictedNo REST API in your plan.
403plan_limitYour plan lacks Recurring card billing (packages) or Payment links (products).
403account_restrictedThe account is read-only (POST).
404not_foundCheckout session not found.
409idempotency_conflictIdempotency-Key reused with a different body (POST).
401 / 429 / 500unauthorized / rate_limited / internal_errorSee Errors