Migration — 2025-11-01 to 2026-02-01
This is the template for every QairoPay API version migration. The structure — what changed, why it matters, how to fix, when you can ship — repeats for every future version. The specific example below is 2025-11-01 → 2026-02-01.
Deprecation date for 2025-11-01: 2027-02-01. After that date, requests pinned to 2025-11-01 will receive 410 Gone.
At a glance
| Change | Type | Impact |
|---|---|---|
Idempotency-Key required on every write | Breaking | Most-impacted clients — every POST integration |
Signature header renamed X-QairoPay-Signature → QairoPay-Signature | Breaking | Anyone with custom webhook verification (SDK users unaffected) |
card.transaction.merchant.category_code returns raw MCC | Breaking | Anyone reading MCC labels server-side |
| Real-time auth endpoint added | Additive | None |
| Bulk endpoints added | Additive | None |
| Async iterator in TS SDK | Additive | None |
Required: Idempotency-Key on every write
Before the upgrade, the API accepted POST requests without an Idempotency-Key header and processed them once. Starting 2026-02-01, a missing key returns:
HTTP/1.1 400 Bad Request{ "error": { "type": "invalid_parameter", "code": "missing_field", "param": "Idempotency-Key", "message": "Idempotency-Key header is required on all POST endpoints." }}How to fix
Add the header to every POST. Generate a UUID per logical operation — see Idempotency for the persistence pattern.
Before
curl https://api.qairopay.com/v1/passes \ -H "Authorization: Bearer $KEY" \ -d '{ "template_id": "tpl_..." }'After
curl https://api.qairopay.com/v1/passes \ -H "Authorization: Bearer $KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "template_id": "tpl_..." }'Before
await qp.passes.create({ template_id: "tpl_..." });After
await qp.passes.create( { template_id: "tpl_..." }, { idempotencyKey: crypto.randomUUID() },);Required: rename webhook signature header
The signature header used to be X-QairoPay-Signature (with X- prefix). The IETF deprecated the X- prefix in 2012 and we’ve finally caught up.
How to fix
Read the new header name.
Before
const sig = req.headers.get("X-QairoPay-Signature");After
const sig = req.headers.get("QairoPay-Signature");The SDK handles this for you — QairoPay.webhooks.constructEvent() reads whichever header is present.
Required: merchant.category_code returns raw MCC
Previously the card-transaction event’s merchant.category_code field returned a QairoPay-normalized label like "restaurants". From 2026-02-01 it returns the four-digit MCC string like "5812".
How to fix
If you mapped labels in your code, switch to mapping MCCs. The processor’s MCC list is stable; the normalized labels were a QairoPay convenience that hid useful information.
Before
if (txn.merchant.category_code === "restaurants") { /* … */ }After
import { isRestaurantMCC } from "./mcc.js";if (isRestaurantMCC(txn.merchant.category_code)) { /* … */ }The TS SDK ships a MCC helper module with the most common groupings pre-defined.
How to upgrade safely
- Read this page end-to-end and identify which breaking changes apply to your integration.
- In sandbox, set the per-request override
QairoPay-Version: 2026-02-01on the calls your tests exercise. Run the full suite. - Fix any failures using the recipes above.
- Once tests pass, switch your sandbox tenant’s pinned version in the dashboard to
2026-02-01. - Soak for at least 48 hours. Watch the dashboard’s API errors chart for regressions.
- Switch your live tenant’s pinned version. There is no downtime; versions are negotiated per-request.
- If a regression appears, switch the pinned version back. Already-completed requests are unaffected.
Rollback
You can roll back at any time by switching the pinned version in the dashboard. Requests in flight when you flip the pin complete on whichever version they started on.
What’s coming next
The next API version is currently scheduled for Q3. We’ll publish a similar migration guide at least 30 days before it goes live. Subscribe to the Changelog RSS feed (/changelog.xml) to get notified.