For your engineering team

Connect your back end to Reconaxe. Plain JSON, signed webhooks.

Your back end sends customers, cases, KYC files and the customer's replies. Reconaxe sends back case references, due dates and every update your app should show. One API key, plain JSON and CSV, and signed webhooks.

  • OpenAPI 3.1 contracts
  • Checked against the service on every build
  • Safe retries, nothing created twice
  • Delivery retried for 24 hours
Raise a case from your app
curl -X POST "$SANAD_URL/v1/cases" \
  -H "X-Api-Key: $SANAD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "submission_id": "app-7731",
    "customer_no": "SME-100245",
    "request_type": "COMPLAINT",
    "topic": "COLLECTION_CALLS",
    "title": "Collection calls after I paid"
  }'

# 201 delivered
# { "status": "delivered", "case": { "caseId": "CS-261027-K2P9",
#   "priority": "p1", "respondBy": "…", "dueAt": "…" } }

Quickstart

Four steps from an API key to a case on an agent's screen and its update back in your app. Every example uses the demo connector profile's field names; at onboarding you get the same contract in your own.

  1. Get a key and an address

    At onboarding Reconaxe gives your team an API key and the address of your test environment. Send the key in X-Api-Key (or Authorization: Bearer). Reconaxe keeps only its SHA-256, and can limit calls to your networks.

    Running Reconaxe yourself? The local stack answers on port 8090 with the demo key below.

    Your shell
    export SANAD_URL=http://localhost:8090            # or your test environment
    export SANAD_API_KEY=demo-partner-key-not-for-production
  2. Send a customer profile

    Send the full current profile when a business finishes onboarding and whenever it changes. Include the time it changed: an older change than the last one Reconaxe applied is ignored, so messages can arrive out of order safely.

    The answer says what happened: created, updated, unchanged, linked or ignored_older.

    PUT /v1/customers/{customerNo}
    curl -X PUT "$SANAD_URL/v1/customers/SME-100245" \
      -H "X-Api-Key: $SANAD_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "legal_name_en": "Al-Waha Trading Est.",
        "cr_number": "1010000419",
        "mobile": "+966 11 000 0419",
        "segment": "SMALL",
        "city_code": "RUH",
        "language": "AR",
        "account_status": "A",
        "updated_at": "2026-10-27T09:30:00Z"
      }'
  3. Raise a case from your app

    Name each case with your own submission_id. Sending it again returns the first case, so retry freely after a timeout. Reconaxe answers with the case reference and the dates SAMA's rules set; the priority follows the topic.

    A case can even arrive before its profile: it gets its reference at once and moves to the customer when the profile lands.

    201 · the answer
    {
      "messageId": "9b2f…",
      "status": "delivered",
      "receivedAt": "2026-10-27T10:02:11Z",
      "attempts": 1,
      "case": {
        "caseId": "CS-261027-K2P9",      // show this to the customer
        "customerLinked": true,
        "priority": "p1",             // follows the topic
        "respondBy": "…",             // the customer hears from Reconaxe by then
        "dueAt": "2026-11-03T10:02:11Z"    // 5 SAMA working days
      }
    }
  4. Hear back through a signed webhook

    When an agent replies, needs more information, resolves or reopens the case, Reconaxe posts case.updated to your webhook. Changes an agent makes to a customer arrive as customer.changed.

    • Check X-Sanad-Signature and refuse a t older than 5 minutes.
    • X-Sanad-Message-Id repeats on a retry: ignore ids you have stored.
    • Apply an update only if its sequence is higher than the last one.
    • Answer 2xx once stored. Anything else is retried for 24 hours.
    Verify X-Sanad-Signature
    import crypto from 'node:crypto';
    
    // rawBody: the exact bytes received, e.g. express.raw({ type: 'application/json' })
    // header:  "t=1761559200,v1=5f0c…"
    export function isFromSanad(rawBody, header, secret) {
      const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
      const t = Number(parts.t);
      if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false;
      const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest();
      const given = Buffer.from(parts.v1 ?? '', 'hex');
      return given.length === expected.length && crypto.timingSafeEqual(given, expected);
    }

The nine flows

Everything a partner exchanges with Reconaxe, in the order most teams build it. Each one is a call in the reference, with examples, every answer and every error.

FlowDirectionCallWhen
1 Customer profileto ReconaxePUT/v1/customers/{customerNo}A business finishes onboarding, or its profile changes
2 New caseto ReconaxePOST/v1/casesA customer raises a complaint, question or request in your app
3 KYC fileto ReconaxePOST/v1/kyc-files CSVOnce a day, before the deadline set in Reconaxe (default 23:00 Riyadh)
4 Changes made in Reconaxeto youwebhook customer.changedWithin a minute of an agent changing one of your customers
5 Nightly customer listto ReconaxePOST/v1/reconciliations CSVOnce a night; Reconaxe answers with its comparison
6 Read full detailsto ReconaxeGET/v1/customers/{customerNo}When you need Reconaxe's current record (off unless allowed)
7 Case updatesto youwebhook case.updatedReceived, more information needed, reply, resolved, closed, reopened
8 Evidence fileto ReconaxePOST/v1/cases/{submissionId}/filesThe customer attaches a PDF, JPG or PNG (up to 10 MB)
9 Customer replyto ReconaxePOST/v1/cases/{submissionId}/repliesThe customer answers in your app

Rules you can build on

The behaviour your code can rely on, so you write less of it.

Every answer says where it stands
delivered Reconaxe applied it; its reply is attached.
queued Stored. Reconaxe delivers it for you and you can ask about it.
rejected errors names each field, in your own field names.
Retries are always safe

Profiles carry their time of change, cases a submission_id, files an uploadId and replies a reply_id. Sending the same message twice never creates anything twice.

Reconaxe keeps only what it needs

Fields Reconaxe does not keep, such as a bank account number, are dropped and listed by name in droppedFields. Messages are stored encrypted and cleared once delivered.

Your names, your codes

Your connector profile maps your field names and code values to Reconaxe's. You never rename a column to fit us, and errors come back in your names.

Files are checked before anyone opens them

Every evidence file is scanned for malware. A file that fails is blocked, its content deleted, and your app hears file_blocked with a request for a clean copy.

Errors you can act on

Problems found before a message is accepted (missing key, wrong address, unreadable input) are RFC 9457 problem details with a stable code.

Planning an integration?

Talk to the team that built it. We will map your fields, set up your connector profile and test environment, and send you the contract in your own names.

Talk to the integration team