Quickstart
This guide walks the whole integration path: get credentials from the merchant's Quroosh dashboard, prove the connection works, sync and map branches, sync a customer, and send your first sale event. The coding part takes about 15 minutes; two short steps happen in the Quroosh dashboard on the merchant's side.
How the integration fits together
Two systems talk in both directions: your platform calls the Quroosh Partner API, and Quroosh pushes webhooks to your platform's receiver endpoint. Some steps are yours; some belong to the merchant in their Quroosh dashboard:
| # | Where | What happens |
|---|---|---|
| 1 | Quroosh dashboard (merchant) | Merchant activates the integration and gets an API key + signing secret — shown once — and hands them to you. |
| 2 | Your platform | Store both values, then call GET /connection to prove the connection works. |
| 3 | Your platform | Send a BranchSynced event for each of your branches. |
| 4 | Quroosh dashboard (merchant) | Merchant pairs each of your branches with a Quroosh branch. Sales are rejected until this is done. |
| 5 | Your platform | Send CustomerSynced for your customers, then InvoiceEarned on every sale. Points land automatically. |
| 6 | Quroosh → your platform | Quroosh pushes webhooks (loyalty rule changes, integration status) to your receiver endpoint, signed with the same secret. The X-TenantId header tells you which of your tenants the delivery belongs to. |
1. Get your credentials (Quroosh dashboard)
Credentials are issued by the merchant, not by Quroosh support. In the Quroosh merchant dashboard, the merchant opensPartner Integrations (sidebar → Partner Integrations), selects your platform and which of your deployments to target (testing or production — your webhook receiver URL for each is registered with Quroosh when the partnership is set up), enters the merchant's tenant identifier on your side, and clicks activate. The dashboard then shows two values exactly once:
| Value | Looks like | You use it to |
|---|---|---|
| API key | qpk_9f2a… | Send in the X-Api-Key header on every request. It identifies the merchant — never send a merchant ID in the body. |
| Signing secret | random string | Compute the X-Signature header on POST /events, and verify the signature on webhooks Quroosh sends you. |
Quroosh keeps only a hash of the key and never displays the raw values again. Store both in your server-side secret store. If a value is lost, the merchant rotates from the same page: rotation issues a new value while the old one keeps working until it is explicitly revoked, so cut-over is zero-downtime.
Staging and production are separate: the merchant activates on each environment's dashboard and each issues its own credentials. A staging key only authenticates against api-staging.quroosh.io.
2. Test the connection
GET /connection is the connection test. A green response proves your key, tenant resolution, and the integration state end-to-end, and returns the merchant's store name — show it as "Connected to …" so a pasted key for the wrong account is caught immediately. No signature needed on GET requests — only the API key:
curl https://api-staging.quroosh.io/api/partner/v1/connection \
-H "X-Api-Key: qpk_XXXXXXXXXXXX"200 OK:
{
"connected": true,
"storeId": "0195d0a0-0000-7000-8000-0000000000aa",
"storeName": "Karam Coffee",
"platform": "Kosoor",
"environment": "Production"
}environment is the deployment on your side that Quroosh delivers webhooks to — if it says Testing when you expected production, the merchant picked the wrong deployment at activation.
If it fails instead, every error uses the same envelope ({ "code", "message", "correlationId" }):
| Status | Code | Fix |
|---|---|---|
| 401 | MISSING_API_KEY | The X-Api-Key header is absent. |
| 401 | INVALID_API_KEY | Key unknown or revoked — re-check the value, or ask the merchant to rotate. |
| 401 | INTEGRATION_NOT_CONNECTED | The merchant hasn't activated the integration yet (step 1). |
| 503 | INTEGRATION_PAUSED | The merchant paused the integration. Credentials stay valid — retry later. |
3. Sync your branches
Everything you push into Quroosh goes through one endpoint:POST /api/partner/v1/events. Each request is anevent envelope signed with your secret. Write the sender once and reuse it for every event type in this guide:
using System.Net.Http.Json;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
async Task<EventAckResponse?> SendEventAsync(
HttpClient client, string apiKey, string signingSecret,
string eventType, string entityType, string entityId, object payload)
{
// Serialize once so the bytes you sign are the exact bytes you send.
var rawBody = JsonSerializer.SerializeToUtf8Bytes(new
{
eventId = Guid.CreateVersion7(), // dedup key — one per business event
occurredAt = DateTimeOffset.UtcNow,
entityType,
entityId,
sourceSystem = "Kosoor", // your platform identifier
schemaVersion = 1,
correlationId = Guid.CreateVersion7(),
eventType,
payload
});
// X-Signature: t=<unix_ts>,v1=<hex> — HMAC-SHA256 over "{timestamp}." + raw body.
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 signature = $"t={timestamp},v1={Convert.ToHexStringLower(hmac.GetHashAndReset())}";
using var content = new ByteArrayContent(rawBody);
content.Headers.ContentType = new("application/json");
using var request = new HttpRequestMessage(HttpMethod.Post, "api/partner/v1/events")
{
Content = content
};
request.Headers.Add("X-Api-Key", apiKey);
request.Headers.Add("X-Signature", signature);
using var response = await client.SendAsync(request);
return await response.Content.ReadFromJsonAsync<EventAckResponse>();
}The HMAC covers the exact byte sequence Quroosh receives. Re-serializing the JSON between hashing and sending (whitespace, key order) breaks verification — hold onto the byte[] and reuse it for both.
Now announce each of your branches with a BranchSynced event:
var branchId = Guid.Parse("7c9e6679-7425-40de-963d-0d5f8b1a2c11"); // your branch's ID
var ack = await SendEventAsync(client, apiKey, signingSecret,
eventType: "BranchSynced",
entityType: "Branch",
entityId: branchId.ToString(),
payload: new
{
externalBranchId = branchId,
name = "Olaya Branch",
isActive = true
});200 OK:
{
"accepted": true,
"eventId": "0195d1c7-6e40-7f8e-9a2b-000000000001",
"alreadyProcessed": false
}accepted means the event was recorded. Resending the sameeventId replays the cached acknowledgement withalreadyProcessed: true — the handler never runs twice, so retries are always safe.
4. The merchant maps the branches (Quroosh dashboard)
Each synced branch arrives in Quroosh as Unmapped. Quroosh does not prompt anyone — the merchant opens Partner Integrations → Branch mappings, picks the matching Quroosh branch for each of your branches, and confirms. The mapping is edited only there, so the two sides can never disagree about which branch is which.
You can watch the status from your side at any time:
curl https://api-staging.quroosh.io/api/partner/v1/branch-mappings \
-H "X-Api-Key: qpk_XXXXXXXXXXXX"{
"mappings": [
{
"externalBranchId": "7c9e6679-7425-40de-963d-0d5f8b1a2c11",
"externalBranchName": "Olaya Branch",
"status": "Mapped",
"mappedStoreId": "0195d0a0-0000-7000-8000-0000000000aa",
"mappedStoreName": "Karam Coffee",
"mappedBranchId": "0195d0a0-0000-7000-8000-000000000001",
"mappedBranchName": "Olaya"
}
]
}An InvoiceEarned for an unknown branch fails withBRANCH_MAPPING_NOT_FOUND; for a synced-but-unconfirmed branch, BRANCH_MAPPING_UNCONFIRMED. Failed events are never cached — once the merchant confirms the mapping, resend the same event (same eventId) and it succeeds.
5. Sync a customer
Quroosh matches earns to loyalty members by your customer ID, so a customer must be synced before their first invoice. Same sender, different payload:
var ack = await SendEventAsync(client, apiKey, signingSecret,
eventType: "CustomerSynced",
entityType: "Customer",
entityId: "cust_012345",
payload: new
{
externalCustomerId = "cust_012345",
phone = "+966501234567",
firstName = "Sara",
lastName = "Alqahtani"
});Send one on customer create and update. The phone number is how Quroosh links your customer to their loyalty account.
6. Send your first sale
The moment you've been building toward — a customer earns points on a 104.35 SAR invoice:
var invoiceId = Guid.CreateVersion7();
var ack = await SendEventAsync(client, apiKey, signingSecret,
eventType: "InvoiceEarned",
entityType: "Invoice",
entityId: invoiceId.ToString(),
payload: new
{
externalInvoiceId = invoiceId,
externalCustomerId = "cust_012345",
externalBranchId = branchId,
subtotalAfterDiscountBeforeVat = 104.35m,
earnType = "Points",
stampCardId = (Guid?)null,
channel = "Pos"
});
Console.WriteLine($"accepted={ack!.Accepted} alreadyProcessed={ack.AlreadyProcessed}");accepted: true and the customer's balance moves. From here, fire InvoiceEarned on every completed sale.
7. What to build next
- Checkout lookup —
GET /customers/lookup?phone=…returns the customer's balance, tier, redeemable rewards, and stamp progress live at the till. See Customer lookup. - Products and coupons — send
ProductSyncedandCouponSyncedthe same way you synced branches, plusCouponUsed/CouponUsageCancelledon use. The merchant then maps Quroosh rewards to your products from their reward editor. See Event types for every payload. - Receive webhooks — Quroosh pushes
LoyaltyRuleChangedandIntegrationStatusChangedto your registered receiver endpoint, signed with the sameX-Signaturescheme; route each delivery by itsX-TenantIdheader. See Webhooksfor the verification snippet and retry ladder.
Go-live checklist
GET /connectionreturns the merchant's store name on staging.- Every branch shows
MappedinGET /branch-mappings. - Customers sync on create and update.
- An
InvoiceEarnedis accepted, and resending the sameeventIdreturnsalreadyProcessed: true. - Your webhook endpoint verifies
X-Signatureand responds 2xx. - The merchant activates on production and you swap in the production credentials.
Keep reading
- Authentication — the signature scheme in detail, key rotation, error responses.
- Event types — the full catalogue of 16 events and their payload shapes.
- Errors — every error code, including the event-processing codes.
- API Reference — request/response schemas with a try-it playground.