Commerce API
The Commerce API plan lets partners embed Sellub payments and checkout in their own apps and websites. Today that means:
- External payments — a REST API for one-off payments, hosted invoices and recurring subscriptions (Paystack plans), authenticated with a publishable key.
- Webhooks — signed events for orders, payments, fulfilment, subscriptions and stock.
- Sandbox — a separate test channel and
pk_test_keys that run on Paystack test mode. - The standard Shop API for catalog and cart — see API reference.
The easiest way in is @duabalabs/sellub-client.
Keys
Create keys in the dashboard: Studio → Integrate. Every key belongs to one shop
(identified by its public account id, acct_…) and is shown once — only a hash is
stored.
| Prefix | Use | Today |
|---|---|---|
pk_live_ / pk_test_ | Browser-safe; pinned to your allowed origins | Authenticates the external-payments API |
sk_live_ / sk_test_ | Server-side only | Can be created; no endpoint accepts it yet |
A pk_test_ key routes payments to Paystack test mode, so it can never move real money.
Send the publishable key in the X-Sellub-Publishable-Key header, with your accountId
in the request:
curl -X POST https://api.sellub.com/external-payments/initialize \
-H "Content-Type: application/json" \
-H "X-Sellub-Publishable-Key: pk_live_…" \
-H "Origin: https://your-site.com" \
-d '{"accountId":"acct_…","email":"buyer@example.com","amount":5000}'Requests must come from an origin on your allow-list (set on the Integrate page;
https://*.example.com wildcards allowed), and any callback URL must be on your callback
allow-list and use HTTPS. Each key is rate-limited to 10 requests a
minute per IP and 100 per account.
External payments API
Base URL https://api.sellub.com/external-payments.
| Method | Path | Does |
|---|---|---|
POST | /initialize | Start a one-off payment; returns a Paystack authorization URL |
GET | /verify/:reference | Check a payment’s status |
POST | /subscriptions/plans · GET /subscriptions/plans | Create / list your subscription plans |
POST | /subscriptions/start | Start a subscription for a customer |
GET | /subscriptions/:code · /subscriptions/by-reference/:reference | Look up a subscription |
POST | /subscriptions/cancel | Cancel (needs the customer’s email token) |
POST | /invoices · GET /invoices/:code | Create / read a hosted invoice |
Money goes straight to your Paystack subaccount; Sellub’s fee is taken automatically by Paystack at your plan’s rate.
Plans
Commerce API access comes with the COMMERCE_API plan — Developer (GHS 500/mo, 1.5%),
Growth (GHS 1,500/mo, 1%) or Enterprise (negotiated). The plan is billed monthly from your
Sellub wallet:
active → grace_period → downgraded
→ cancelledactive— paid; everything works.grace_period— a renewal failed; everything still works for up to 7 days while you top up.downgraded— the grace period ran out; you’re back on the free Marketplace plan.cancelled— cancelled by you or by Sellub.
Webhook signing
Register an endpoint on the Integrate page and choose its events. You get a signing
secret (whsec_…) for it, which you can rotate there.
Headers
Sellub-Event: order.placed
Sellub-Event-Id: <event id>
Sellub-Signature: t=1714050000,v1=4f4d…Signed payload
${eventId}.${timestamp}.${rawJsonBody}timestamp is the t= value from Sellub-Signature, and v1 is the lowercase hex of
HMAC_SHA256(secret, signedPayload).
Verification (Node)
import crypto from "node:crypto";
function verify(req, secret) {
const parts = Object.fromEntries(
req.headers["sellub-signature"].split(",").map((p) => p.split("=")),
);
const id = req.headers["sellub-event-id"];
// 5-minute tolerance to defeat replay attacks
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) throw new Error("stale");
const expected = crypto
.createHmac("sha256", secret)
.update(`${id}.${parts.t}.${req.rawBody}`)
.digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(parts.v1 ?? ""), Buffer.from(expected))) {
throw new Error("bad signature");
}
}Or let @duabalabs/sellub-webhooks (0.4.0 or later)
do it, typed event included:
verifyAndParse({ secret, payload: rawBody, headers: req.headers }).
Delivery semantics
- At-least-once — dedupe on
Sellub-Event-Id. - Timeout — 10 seconds per attempt.
- Retries — up to 6 attempts:
30s, 2m, 10m, 1h, 6h, 24h, then the delivery is marked failed. - Auto-disable — an endpoint whose last 5 deliveries all failed is switched off.
- Delivery log — every attempt is listed per endpoint on the Integrate page.
Events
order.placed, order.state_changed, order.refunded, payment.succeeded,
payment.failed, fulfillment.shipped, fulfillment.delivered,
subscription.activated, subscription.renewed, subscription.cancelled,
inventory.updated, inventory.low_stock, inventory.out_of_stock, stock.movement.
Payload types are in @duabalabs/sellub-types.