PayBeta API v1
Merchant Checkout Integration
Offer PayBeta Buy Now Pay Later at your checkout in three steps: create an API key, open a hosted checkout session from your backend, and receive a signed webhook when the customer's plan is approved.
0. Drop-in plugin (no backend code)
The fastest way to accept PayBeta: add our plugin script to your checkout page and the hosted card page opens automatically when a customer clicks the button — no server-side API call required. The script tag uses your public merchant ID (shown on your Merchant → Developers page), never a secret key.
<script src="https://paytbeta.com/paybeta.js"
data-paybeta-key="YOUR_MERCHANT_ID" async></script>
<div data-paybeta-button
data-amount="1299.00"
data-reference="order_10231"
data-description="Order #10231"
data-customer-email="buyer@example.com"
data-success-url="https://yourstore.com/thanks"
data-cancel-url="https://yourstore.com/cart"></div>Clicking the button opens PayBeta's secure hosted checkout in a popup (full-page redirect if popups are blocked). When the customer completes payment, the plugin automatically returns them to your success_url withpaybeta_session andreference query parameters. Always confirm the order on your server via the webhook before releasing goods.
Programmatic usage (custom button)
window.addEventListener("paybeta:completed", (e) => {
console.log("Paid!", e.detail.reference); // fulfil via your webhook
});
document.querySelector("#buy").addEventListener("click", () => {
PayBeta.checkout({
amount: cart.total,
reference: cart.orderId,
successUrl: "https://yourstore.com/thanks",
}).catch((err) => alert(err.message));
});Events
paybeta:completed— payment confirmed; detail includes sessionId, reference, planId.paybeta:cancelled— the customer closed or the session expired.paybeta:error— the session could not be created (e.g. duplicate reference).
Under the hood the plugin calls POST /api/public/embed/checkout, a browser-safe endpoint that only accepts your public merchant ID. For full control (custom flows, server-rendered redirects), use the authenticated API below.
1. Getting started
Sign in as a PayBeta merchant, open Merchant → Developers, and create your first API key. Save your Live and Sandbox keys somewhere safe — they are only shown once.
Base URL for every API request:
https://paytbeta.com/api/public/v12. Authentication
All requests are authenticated with a Bearer API key in theAuthorization header. Keys look likepk_live_… (production) orpk_sandbox_… (test).
Authorization: Bearer pk_live_<your-secret>3. Create a checkout session
Call this from your server when a customer chooses PayBeta at checkout. You get back a checkout_url to redirect the customer to.
Request body
{
"amount": 2499.00,
"currency": "ZAR",
"reference": "order_10231",
"order_ref": "10231",
"items": [{ "name": "Sneakers", "qty": 2, "price": 1249.50 }],
"description": "Order #10231 — 2x Sneakers",
"customer_email": "buyer@example.com",
"customer_phone": "+27821234567",
"return_url": "https://yourstore.com/thank-you?ref=10231",
"success_url": "https://yourstore.com/thank-you?ref=10231",
"cancel_url": "https://yourstore.com/cart",
"webhook_url": "https://yourstore.com/api/paybeta-webhook",
"metadata": { "order_id": "10231", "sku": "SN-42" },
"expires_in_minutes": 30
}cURL
curl -X POST https://paytbeta.com/api/public/v1/checkout/sessions \
-H "Authorization: Bearer pk_live_..." \
-H "Content-Type: application/json" \
-d '{"amount":2499,"currency":"ZAR","reference":"order_10231","success_url":"https://yourstore.com/thanks","cancel_url":"https://yourstore.com/cart"}'Node.js
const res = await fetch("https://paytbeta.com/api/public/v1/checkout/sessions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.PAYBETA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
amount: 2499,
currency: "ZAR",
reference: order.id,
success_url: `${site}/thanks?o=${order.id}`,
cancel_url: `${site}/cart`,
metadata: { order_id: order.id },
}),
});
const session = await res.json();
return Response.redirect(session.checkout_url, 303);Response (201)
{
"id": "b3f1…-uuid",
"object": "checkout.session",
"status": "created",
"amount": 2499,
"currency": "ZAR",
"reference": "order_10231",
"checkout_url": "https://paytbeta.com/checkout/b3f1…-uuid",
"expires_at": "2026-07-23T13:00:00Z"
}4. Redirect the customer
Send the customer to session.checkout_url. PayBeta signs them in, runs credit checks, presents plan options (3 / 6 / 12 installments, weekly/bi-weekly/monthly), and captures the NCA pre-agreement acceptance. On success we forward them to your success_url.
5. Retrieve a session or plan
curl https://paytbeta.com/api/public/v1/plans/PLAN_ID \
-H "Authorization: Bearer pk_live_..."Plan response
{
"id": "…",
"object": "plan",
"plan_number": "KP12345678",
"status": "active",
"total_amount": 2499,
"outstanding_balance": 1666,
"collection_frequency": "monthly",
"total_instalments": 3,
"instalments": [
{ "number": 1, "amount": 833, "due_date": "2026-07-23", "status": "paid" },
{ "number": 2, "amount": 833, "due_date": "2026-08-23", "status": "pending" },
{ "number": 3, "amount": 833, "due_date": "2026-09-23", "status": "pending" }
]
}6. Webhooks
Configure your webhook URL and signing secret under Merchant → Developers. Every event POSTs JSON with a PayBeta-Signature header.
Event types
order.paid— first payment verified with the card provider; safe to release the order. Retried up to 5 times with backoff until your endpoint returns 2xx.paybeta.plan.activated— the shopper's first payment cleared and the plan is live. Auto-confirm and fulfil the order on this event. Sent as a flat payload (no envelope).checkout.session.completed— customer finished the hosted checkout and a plan was created.plan.approved— the plan is active and first collection scheduled.plan.paid— the final instalment has been collected.instalment.paid— a scheduled instalment was collected.instalment.failed— a scheduled instalment failed (retry in progress).
Signature header
PayBeta-Signature: t=1700000000,v1=<hex sha256 of raw body using your webhook secret>Verifying in Node.js
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(rawBody, header, secret) {
const [, , sig] = /t=(\d+),v1=([a-f0-9]+)/.exec(header ?? "") ?? [];
if (!sig) return false;
const expected = createHmac("sha256", secret).update(rawBody).digest("hex");
const a = Buffer.from(sig, "hex");
const b = Buffer.from(expected, "hex");
return a.length === b.length && timingSafeEqual(a, b);
}paybeta.plan.activated payload (flat — auto-confirm the order on this)
{
"event": "paybeta.plan.activated",
"timestamp": "2026-09-13T08:00:00.000Z",
"plan_id": "…uuid",
"transaction_ref": "KP00700052",
"consumer_ref": "PBC-1a2b3c4d",
"merchant_id": "…uuid",
"basket_amount": 300,
"first_payment_amount": 100,
"first_payment_status": "collected",
"remaining_instalments": [
{ "amount": 100, "due_date": "2026-09-27", "status": "scheduled" },
{ "amount": 100, "due_date": "2026-10-11", "status": "scheduled" }
],
"plan_status": "active",
"currency": "ZAR"
}Your endpoint must return a 2xx quickly and match orders by transaction_ref or the reference you set on the checkout session. Verify the signature before trusting the payload. You can fire a sample event any time from Merchant → Developers → Send test order event.
Envelope example (other events)
{
"id": "evt_…",
"type": "checkout.session.completed",
"created": 1700000000,
"livemode": true,
"data": {
"session": { "id": "…", "reference": "order_10231", "amount": 2499, "metadata": {} },
"plan": { "id": "…", "total_amount": 2499 }
}
}7. Testing (sandbox)
Use a pk_sandbox_… key to create sessions in the sandbox environment. Sandbox sessions use PayBeta's mock PSP so no real money moves, and webhooks are dispatched to your test webhook URL if you set one (otherwise the live URL). Sandbox events include "livemode": false.
8. Errors
Errors return a JSON body and a non-2xx status:
{ "error": { "code": "invalid_request", "message": "amount must be positive" } }| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | Payload validation failed. |
| 401 | unauthorized | Missing/invalid API key. |
| 404 | not_found | Session or plan does not belong to your merchant. |
| 409 | duplicate_reference | reference already exists for this merchant. |
| 500 | server_error | Retry with exponential back-off. |