Skip to content
Ask the docs

Find answers across the QairoPay docs.

Type a question and we'll synthesize an answer from the docs with citations back to the source pages.

TypeScript SDK

The TypeScript SDK is the canonical client for the QairoPay API. It’s fully typed, handles idempotency keys, retries transient errors, verifies webhook signatures, and works across Node, Bun, Deno, and edge runtimes.

Install

Terminal window
pnpm add @qairopay/sdk

Initialize

import { QairoPay } from "@qairopay/sdk";
const qp = new QairoPay({
apiKey: process.env.QAIROPAY_KEY!,
// optional — auto-detected from key prefix, override only if needed
// baseUrl: "https://api.sandbox.qairopay.com",
// apiVersion: "2026-05-19",
// maxRetries: 3,
// timeoutMs: 15000,
});

The SDK reads the environment (sandbox vs live) from the key prefix. You don’t need to set baseUrl unless you’re hitting a custom mirror.

Issuing a pass

const template = await qp.passTemplates.create({
name: "Hello Pass",
kind: "loyalty",
brand: { background_color: "#1F7A5A", foreground_color: "#FCFAF6" },
});
// SDK auto-attaches an Idempotency-Key (UUID v4) — pass one explicitly
// only if you need to derive it from your own request id.
const pass = await qp.passes.create({
template_id: template.id,
holder: { email: "you@example.com", name: "Jane Doe" },
});
console.log(pass.download.apple_url);

Observability hooks

The SDK fires three lifecycle hooks per logical operation (FR-042). They’re the seam for OpenTelemetry, audit logging, and metrics — they fire at the operation boundary, not per raw fetch, so retries don’t double-count.

import { QairoPay } from "@qairopay/sdk";
import { trace } from "@opentelemetry/api";
const tracer = trace.getTracer("qairopay-sdk");
const spans = new Map<string, ReturnType<typeof tracer.startSpan>>();
const qp = new QairoPay({
apiKey: process.env.QAIROPAY_KEY!,
hooks: {
onRequest: (ctx) => {
const span = tracer.startSpan(`qairopay ${ctx.method} ${ctx.url}`, {
attributes: {
"qairopay.operation_id": ctx.operationId,
"qairopay.api_version": ctx.apiVersion,
"qairopay.idempotency_key": ctx.idempotencyKey ?? "",
},
});
spans.set(ctx.operationId, span);
},
onRetry: (ctx, lastError, attempt) => {
spans.get(ctx.operationId)?.addEvent("retry", {
attempt,
error_type: lastError.type,
status: lastError.status,
});
},
onResponse: (ctx, res) => {
const span = spans.get(ctx.operationId);
if (!span) return;
span.setAttributes({
"http.status_code": res.status,
"qairopay.request_id": res.requestId ?? "",
"qairopay.attempts": res.attempts,
"duration_ms": res.durationMs,
});
if (res.error) span.recordException(res.error);
span.end();
spans.delete(ctx.operationId);
},
},
});

Guarantees:

  • onRequest fires exactly once per logical operation, regardless of retry count.
  • onRetry fires before each retry with the failed error and the upcoming attempt number.
  • onResponse fires exactly once at the end — on success or after retries are exhausted (in which case res.error is populated).
  • A hook that throws or returns a rejected Promise is caught — it never breaks the call site.
  • Sensitive values are redacted before the context reaches your hook: the Authorization header is masked, card display tokens and provisioning payloads are replaced with placeholders, and webhook signing secrets are masked to *** in responses.

Iterating list endpoints

// Auto-paginate — yields every record across every page, lazily.
// Break out of the loop and the SDK stops fetching.
for await (const pass of qp.passes.list({ status: "installed" })) {
console.log(pass.id);
}
// Page-aware control — useful for checkpointing or backpressure.
const iter = qp.passes.list({ status: "installed" });
const page = await iter.page(); // { data, has_more, next_cursor }

The iterator handles cursor pagination automatically. Pass limit to control page size (default 25, max 100). See Pagination for the full reference.

Error handling

Errors are typed exceptions. Catch the specific type you care about:

import { QairoPayError, RateLimitError, InvalidParameterError } from "@qairopay/sdk";
try {
await qp.passes.create(payload);
} catch (err) {
if (err instanceof InvalidParameterError) {
console.error(`Bad field: ${err.param}${err.message}`);
} else if (err instanceof RateLimitError) {
await sleep(err.retryAfterMs);
} else if (err instanceof QairoPayError) {
console.error(err.requestId, err.type, err.code);
} else {
throw err;
}
}

Webhook verification

import { QairoPay } from "@qairopay/sdk";
// constructEvent is async — Web Crypto's HMAC primitives return Promises.
const event = await QairoPay.webhooks.constructEvent(
rawBody,
signatureHeader,
process.env.QAIROPAY_WEBHOOK_SECRET!,
);
// event is fully typed — switch on event.type for narrowing
if (event.type === "pass.installed") {
await onPassInstalled(event.data.pass);
}

Retries and timeouts

By default, the SDK retries idempotent operations on 5xx, 429, and connection errors with exponential backoff up to 3 attempts. To disable retries for a specific call:

await qp.passes.create(payload, { retries: 0 });

The default request timeout is 15 seconds. Override per-call:

await qp.passes.list({}, { timeoutMs: 30000 });

Edge runtimes

The SDK uses only Web-standard primitives — globalThis.fetch, crypto.subtle, and crypto.randomUUID() — with no node:* imports in the request path. Six runtimes are verified on every CI run (per SC-006):

RuntimeStatusSmoke test
Node.js 20 LTS✅ Supported floorVitest unit + integration
Node.js 22✅ SupportedVitest unit + integration
Node.js 24✅ SupportedVitest unit + integration
Bun 1.x✅ Supportedbun x vitest run against the unit suite
Deno 2.x✅ Supporteddeno check on examples/deno/quickstart.ts
Cloudflare Workers✅ Supportedwrangler dev boots examples/cloudflare-worker/worker.ts

Best-effort (not in CI matrix)

  • Vercel Edge Runtime — shares the V8 Isolate model with Cloudflare Workers; the SDK code paths are byte-identical. Works out of the box, but isn’t on the formal smoke matrix in v1.

Out of scope for v1.0

  • Browsers. A secret API key in a browser bundle is unsafe — the key would be visible to any visitor. A publishable-key-only browser surface is on the roadmap as a separate @qairopay/sdk-browser package; the current package is server-side only.

Other languages

Official SDKs are in active development for Python, Go, Ruby, and PHP. Until those ship, the OpenAPI spec is a working source for any code generator (we recommend openapi-typescript or oapi-codegen).

Source and issues

The SDK is open source on GitHub at qairopay/qairopay-node. Bug reports and pull requests welcome. Security issues should go to security@qairopay.com, not the public tracker.