PayBeta

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/v1

2. 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.

POST/api/public/v1/checkout/sessions

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

GET/api/public/v1/checkout/sessions/{id}
GET/api/public/v1/plans/{id}
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" } }
StatusCodeMeaning
400invalid_requestPayload validation failed.
401unauthorizedMissing/invalid API key.
404not_foundSession or plan does not belong to your merchant.
409duplicate_referencereference already exists for this merchant.
500server_errorRetry with exponential back-off.