ZintlePayments

Documentation

Take your first payment.

Everything below is the real contract: the routes, the statuses, the failure codes, and the rules a correct integration follows.

The three kinds of key

A key is minted in the dashboard and returned exactly once. Only a selector and a hash of the verifier are stored, so there is no reveal endpoint and there never will be.

zk_sk_live_…

Secret key

Server-side only. Full merchant authority, and optionally bound to one wallet so a key separates one project’s money from another’s.

zk_pk_live_…

Publishable key

Safe in a browser or an app, but it cannot create a payment or read your data. It lists the methods available for a corridor and, with a payment’s client secret, confirms and follows that one payment.

zk_rk_live_…

Restricted key

Server-side, limited to an explicit list of permission scopes. One per integration, so a compromise has a blast radius rather than a total.

Which key for which call

Every endpoint, and what each kind of key needs to call it. A restricted key needs the scope shown; a secret key needs nothing extra.

EndpointSecretRestricted — scopePublishable
POST /v1/payment_intentsYespayments.intents.createNo
POST /v1/payment_intents/:id/confirmYespayments.intents.createWith the client secret
GET /v1/payment_intents/:idYespayments.intents.readWith the client secret
GET /v1/payment_intentsYespayments.intents.readNo
POST /v1/customersYescustomers.writeNo
GET /v1/providersYesAny keyYes
POST /v1/payment_linksYesbilling.links.manageNo
GET /v1/payment_links/:idYesbilling.links.readNo
POST /v1/payment_links/:id/deactivateYesbilling.links.manageNo

Using a publishable key

For a checkout you build yourself. Your server creates the payment with the secret key and gives the browser two things: the payment id and its clientSecret. The browser then confirms it and follows its progress with the publishable key, sending the client secret each time. The key is public by design; the client secret is what limits it to the one payment it was handed.

// 1. Your server creates the payment with the SECRET key, and hands the
//    browser only the id and the client secret.
app.post('/pay', async (req, res) => {
  const response = await fetch('https://api.zintle.io/v1/payment_intents', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.ZINTLE_SECRET_KEY}`,
      'Idempotency-Key': `order-${req.body.orderId}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      amount: '500000',
      currency: 'XAF',
      country: 'CM',
      methodType: 'MOBILE_MONEY',
      reference: req.body.orderId,
      customer: req.body.customerId,   // phone and email come from here
    }),
  });
  const intent = await response.json();
  res.json({ id: intent.id, clientSecret: intent.clientSecret });
});

Scopes

A restricted key carries permission codes and may exercise only the intersection of its own scopes and what its merchant is allowed to do. Widening a merchant’s permissions never silently widens a key.

Create one under Developers → API keys: choose Restricted, then tick the scopes. You can only grant scopes you hold yourself. The key works exactly like a secret key for everything in its scopes, and answers 403 for everything else.

# A key for your storefront server that can create checkouts and nothing else:
# scopes billing.links.*  — no refunds, no customer list, no payment history.
curl https://api.zintle.io/v1/payment_links \
  -H "Authorization: Bearer zk_rk_test_7d60534f2a9c1e8b" \
  -H "Content-Type: application/json" \
  -d '{ "amount": "500000", "currency": "XAF", "title": "Order #4471", "reference": "order-4471" }'

Test and live

The environment is encoded in the key itself — zk_sk_test_… or zk_sk_live_… — and is never inferred from a header, a hostname or a request field. A test object is unreachable from a live key and the reverse.

IP allowlist

Each key can name the source addresses permitted to use it. It defaults to empty, meaning any address, because a merchant on dynamic egress would otherwise lock themselves out — but on a fixed egress it is the strongest single control you have.