Skip to Content
API referenceAuthentication
View as Markdown

Authentication

InfraIO Pay has two API surfaces with different auth models. Pick the one that matches who’s calling:

SurfacePath prefixAudienceAuth
Merchant B2B/b2b/v1/*Your serverHMAC-SHA256 request signing
DashboardUsed by the merchant dashboardBrowser sessions for the merchant dashboardBearer JWT

This page covers the B2B surface, the one you call from your server with an API key pair. The dashboard surface is used by the InfraIO Pay merchant dashboard and isn’t a public integration surface.

Always send the full path including the /b2b prefix, and sign that same path (see below).

Endpoints

EnvironmentBase URL
Testhttps://api-dev.infraio.xyz
Livehttps://api.infraio.xyz

Same URL pattern — environment is controlled by the key prefix (pk_test_… vs pk_live_…), not the URL.

Key pair

You get two values from the merchant dashboard (Developers → API keys → + Add key):

  • Publishable key (pk_test_… or pk_live_…) — identifies your account. Sent as X-Client-ID. Safe to embed in your browser bundle (the SDK already does).
  • Secret key (sk_test_… or sk_live_…) — the HMAC signing key. Server-only. Treat it like a database password.

If a secret key ever lands in a browser bundle, git repo, log line, or shared chat — revoke it immediately from the dashboard. Revocation is instant, with no overlap window. Issue a new key and redeploy.

Signing a request

Every call to /b2b/v1/* carries three headers:

X-Client-ID: pk_live_70d0becbd7a53047b0dfe88a960d10ad X-Timestamp: 1715990400 X-Signature: 9a8b7c6d… (hex HMAC-SHA256)

The signature is computed over a canonical string:

METHOD + "\n" + PATH + "\n" + TIMESTAMP + "\n" + BODY
  • METHOD — uppercase HTTP verb (POST, GET, …).
  • PATH — request path including the /b2b prefix, without the host and without the query string (e.g. /b2b/v1/checkout-sessions/quick). The prefix must be present. Query parameters are not signed — for a GET …?cursor=…&limit=20, sign only the path, not the ?… part.
  • TIMESTAMP — unix seconds, as decimal string (e.g. "1715990400"), matching X-Timestamp exactly.
  • BODY — raw request body bytes. Empty string for GET/DELETE.

Sign with HMAC-SHA256 keyed by the secret key, output hex:

import { createHmac } from "node:crypto"; function sign({ method, path, body, secret }: { method: string; path: string; body: string; secret: string; }) { const timestamp = Math.floor(Date.now() / 1000).toString(); const input = [method.toUpperCase(), path, timestamp, body].join("\n"); const signature = createHmac("sha256", secret).update(input).digest("hex"); return { timestamp, signature }; }

Why HMAC, not Bearer?

A bare Bearer-token API ships your only secret over the wire on every request. Anyone who captures one TLS-terminated proxy log gets the keys to your account. HMAC signing means the secret never travels — only its derived signature, which is single-use (bound to that exact request + that exact minute).

The trade-off: you compute a signature for every call. There is no server SDK yet, but the helper above is about 15 lines per language.

Timestamp tolerance

The tolerance is ±5 minutes (300 seconds). A request outside that window is rejected with 401 invalid_signature. Two implications:

  1. Sync your server clock with NTP. A long-running cron with a drifted clock will fail intermittently.
  2. Don’t pre-compute and queue signatures. If a request sits in a retry queue for >5 min, its signature expires.

Key scopes

Secret keys carry one or more of these scope bundles:

ScopeIntended use
readList/read orders, sessions, refunds
write_orderCreate checkout sessions, orders
write_refundIssue refunds, mint refund-request tokens
webhook_manageCreate/update/delete webhook endpoints

The dashboard issues a “full access” key by default (all four scopes). You can mint a restricted-scope key from Developers → API keys → + Add key and tick only the scopes the integration needs.

Scopes are not enforced yet. Scopes are recorded on the key and shown in the dashboard, but any valid sk_… key can call any /b2b/v1/* endpoint for your merchant. Don’t rely on scopes as a security boundary. Rotate or revoke keys to restrict access.

Failed verification

If the signature, X-Client-ID, or timestamp is invalid, the request is rejected with 401 INVALID_SIGNATURE before it reaches the API. Only /b2b/v1/* requests are signed this way. Webhooks use a separate scheme (see below).

What’s next

  • Errors — response shape on 4xx/5xx.
  • Security → API keys — rotation, revocation, what to do if a secret leaks.
  • Webhooks → Signature verification — uses a different HMAC scheme (header X-Signature: sha256=…, signs X-Timestamp + "." + raw_body, plus an optional X-Signature-Prev during the 24-hour rotation grace window). Don’t mix the schemes up — they share the hash algorithm but the signed bytes and the secret family (whsec_… vs sk_…) are different.