Skip to content

Sessions & security

Which key goes where, how a session token is made, and what it may do.

Everything on the browser side of Wajub runs on one short lived string: the session token. This page covers where it comes from, what it replaces, and why the key that creates it must never leave your server.

Three credentials, three places

A Wajub key is a prefix, a dot, then 96 characters. The prefix alone tells you where the key is allowed to live.

CredentialLooks likeLives inCan do
Publishable keypk. or pk_test.Browser, your HTMLCreate a session, nothing else
Secret keysk. or sk_test.Server, environment variableEverything on the API
Session tokenauthorization_tokenBrowser, as sessionIdPay one payment, once

The session token is what makes the other two unnecessary in the browser. It is scoped to a single payment, it carries no account access, and it dies with that payment.

The flow that is correct

Two steps, in this order, every time.

  1. Your server calls POST /payments with the secret key and gets back an authorization_token.
  2. Your browser receives that token, and nothing else, and passes it as sessionId.
curl https://api.wajub.com/payments \
-H "Authorization: sk_test.kZ3qP8mWvL2xR7tB5nY4hC6dF9jS1aG0eU3i…" \
-H "Content-Type: application/json" \
-d '{
  "amount": 35000,
  "currency": "XOF",
  "reference": "ORDER-123",
  "customer": { "email": "buyer@example.com" }
}'

The response carries three things worth keeping.

FieldWhat it is for
authorization_tokenThe browser's sessionId. Send this one down
authorization_urlThe hosted page, for a redirect checkout
transaction.idYour reconciliation key when the webhook lands
Step 2, in the browser
const { sessionId } = await fetch('/api/checkout/session', { method: 'POST' })
  .then((r) => r.json());

await mount('#checkout', { sessionId });

Creating a session from the browser

Wajub(publishableKey).createPayment() exists and works, and it is the right tool for a prototype or a demo page with a fixed price. It is the wrong tool for a shop, because the amount is then decided by code the customer controls.

Sandbox only
const client = Wajub('pk_test.mT9xW2kQ7vB4nL6hR1cY8dF3jS5aG0eU2pA…');

const { sessionId } = await client.createPayment({
  amount: 35000,
  currency: 'XOF',
  customer: { email: 'buyer@example.com' },
});

await client.mount('#checkout');

What the token may do

The session token authorises exactly one payment, and the runtime is what enforces it.

  • It mounts the checkout, opens the overlay, or drives the field components.
  • It reads the session preview through fetchSession(sessionId), which is the same token used as a bearer credential against the checkout origin.
  • It stays valid while the payment is not terminal, and stops shortly after succeeded, failed, expired or cancelled.
  • It survives a remount, so a component that unmounts and comes back reuses the same token.

A terminal token produces onLoadError rather than an empty embed, which is why that callback is worth wiring even on a page you think cannot reach it.

The callback URL

callback is a redirect destination, so it only matters for the redirect flow. Pass a plain HTTPS base URL with no query string of your own. Wajub appends its own.

Query paramValues
statuscomplete, cancelled, failed, expired
referenceThe Wajub transaction reference, when available
trxrefYour own reference, when available
Where the payer lands
https://shop.example.com/order/complete?status=complete&reference=trx_CSUGajfv9xh0XQ5wu2lx&trxref=ORDER-123

An embed never leaves your page, so callback does nothing there. Use onSuccess for the interface and the webhook for the truth.

Multi domain shops

The SDK sends the origin of the current page to the checkout, so a single domain needs no configuration. When your checkout runs on a second domain, name the canonical one explicitly.

Hosted checkout and payment fields
await mount('#checkout', { sessionId, embedOrigin: 'https://shop.example.com' });

const factory = await components(sessionId, {
  componentOrigin: 'https://shop.example.com',
});

Before you go live

RuleWhy
HTTPS on your site and on callbackPayment iframes and redirects refuse plain HTTP
sk. only in server environment variablesNever in a bundle, never in a PUBLIC_ variable
Only authorization_token reaches the browserIt is the one credential with nothing to lose
Mount through the SDKA hand built checkout URL is answered with an embed error
Confirm with a webhookonSuccess is a browser event, and browsers close

What did you think of this content?