Authentication

Every partner request carries two headers that together prove the sender's identity and message integrity:

HeaderPurposeExample
X-Api-KeyIdentifies your partner account and target merchant tenant.qpk_9f2a…
X-SignatureTimestamped HMAC-SHA256 over the raw body. Stripe-style format: t=<unix>,v1=<hex>. Multiple v1= allowed during rotation.t=1752573871,v1=8a4f…c2
The API key identifies the tenant

Never pass a merchant identifier in the request body. The tenant is derived from the API key server-side, so a compromised body cannot cross tenants.

Getting an API key

All credentials are issued by the merchant from thePartner Integrations page in their Quroosh dashboard. Activating the integration issues an API key and a webhook signing secret together; both raw values are shown exactly once and are never retrievable afterwards. Merchants can rotate or revoke either credential at any time from the same page. See the Quickstart for the step-by-step flow.

Keys start with qpk_. Staging and production are separate environments with separate dashboards — a key issued on staging authenticates only against api-staging.quroosh.io, and a production key only against api.quroosh.io.

Computing the signature

Header value format:

X-Signature: t=<unix_timestamp>,v1=<hex_hmac>[,v1=<hex_hmac>...]

Signed payload: "{timestamp}.{raw_body}" — the Unix timestamp, a literal ., then the request's raw bytes. HMAC-SHA256 with your signing secret, output as lowercase hex.

Multiple v1= segments are allowed. During Quroosh's key rotation window Quroosh emits one v1= per active secret; when you rotate your own signing secret you may send two v1=values so Quroosh accepts either.

Timestamp tolerance is ±5 minutes. Sync your server clock (NTP) or requests will be rejected as SIGNATURE_EXPIRED.

using System.Security.Cryptography;
using System.Text;

var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
using var hmac = IncrementalHash.CreateHMAC(
    HashAlgorithmName.SHA256,
    Encoding.UTF8.GetBytes(signingSecret));
hmac.AppendData(Encoding.UTF8.GetBytes($"{timestamp}."));
hmac.AppendData(rawBody);
var mac = hmac.GetHashAndReset();
var signature = $"t={timestamp},v1={Convert.ToHexStringLower(mac)}";
Sign the raw bytes

rawBody must be the exact bytes you send. Any re-serialization between hashing and sending (JSON whitespace change, key order change, encoding conversion) breaks the signature. Hold onto the byte[] and reuse it for both.

Rotating keys

Rotation is zero-downtime: rotating issues a new key and the old key stays valid until the merchant explicitly revokes it from the dashboard — there is no automatic expiry window. Update your platform's configuration with the new key first, then have the merchant revoke the old one. Revoking the last active key is rejected, so the integration can never lock itself out. Signing secrets rotate the same way; during the overlap Quroosh's webhooks carry one v1= signature per active secret.

Failed authentication responses

StatusError codeMeaning
401MISSING_API_KEYThe X-Api-Key header is absent.
401INVALID_API_KEYKey unknown, revoked, or from the wrong environment.
401INVALID_SIGNATUREMissing header, no t= or no v1=, or no matching HMAC under any active secret.
401SIGNATURE_EXPIREDTimestamp outside the 5-minute tolerance window. Check server clock.
401INTEGRATION_NOT_CONNECTEDThe merchant has never activated the integration.
401INTEGRATION_ALREADY_DISCONNECTEDThe merchant disconnected the integration; fresh credentials are required.
503INTEGRATION_PAUSEDThe merchant paused the integration. Credentials stay valid — retry per your backoff schedule.

See Errors for the full code list, including the event-processing codes returned by /api/partner/v1/events.