Skip to content
SAHARIX

Developers

Escrow as an API.

Create partner workspaces, mint scoped keys, and drive escrow through your own product. Webhooks keep your system in sync, and Saharix keeps custody of every naira.

Step 1

Request a workspace

Tell us what you are building and expected volume on the partners page. We reply from partners@saharix.com with a workspace for your team.

Step 2

Mint an API key

Generate a sandbox key in your Saharix partner workspace. The secret is shown once; only a SHA-256 hash is stored server-side. Keys carry scopes: escrows:read, escrows:write, webhooks:read, webhooks:write.

Step 3

Call the API

Send the key as a Bearer token. Escrows you create appear in your dashboard, and every money movement stays inside Saharix ledger controls.

Quickstart

Every request is made against your workspace base URL. Use the sandbox while you build and the production host once your integration is live.

Sandbox

https://api-staging.saharix.com

Test keys (sk_test_...) and simulated funding. No real money moves.

Production

https://api.saharix.com

Live keys (sk_live_...) move real funds under Saharix ledger controls.

Your first request
curl https://api-staging.saharix.com/v1/partner/escrows \
  -H "Authorization: Bearer sk_test_9f2c1a40.<secret>"

Authentication

Every request carries your key as a Bearer token. The key format is keyPrefix.secret. Keys are hashed at rest and can be revoked at any time from your workspace; a revoked key fails with 401.

Request
curl https://api-staging.saharix.com/v1/partner/escrows \
  -H "Authorization: Bearer sk_test_9f2c1a40.<secret>"

Create an escrow

Money-moving creates require an x-idempotency-key header. Replaying the same key with the same body returns the original response instead of a duplicate escrow; reusing a key with a different body returns 409 idempotency_key_conflict. Amounts are in minor units (kobo for NGN).

POST /v1/partner/escrows
curl -X POST https://api-staging.saharix.com/v1/partner/escrows \
  -H "Authorization: Bearer sk_test_9f2c1a40.<secret>" \
  -H "Content-Type: application/json" \
  -H "x-idempotency-key: 7c9e6679-7425-40de-944b-e07fc1f90ae7" \
  -d '{
    "title": "Website redesign",
    "buyer_email": "buyer@acme.com",
    "seller_email": "freelancer@studio.com",
    "amount": 45000000,
    "currency": "NGN",
    "inspection_period_days": 3
  }'

# 201 Created
# { "status": "success", "data": {
#     "id": "0b6f...", "status": "PENDING",
#     "total_amount": "45000000", "currency": "NGN",
#     "partner_ref": null, "created_at": "..." } }

Read escrows

List your escrows with pagination, or fetch a single escrow with its milestones. Both require the escrows:read scope.

GET /v1/partner/escrows?limit=20&offset=0
{ "status": "success", "data": {
    "items": [
      { "id": "0b6f...", "status": "FUNDED", "title": "Website redesign",
        "total_amount": "45000000", "currency": "NGN",
        "partner_ref": null, "milestone_count": 1, "created_at": "..." }
    ],
    "total": 1, "limit": 20, "offset": 0 } }

# GET /v1/partner/escrows/:id adds the milestone list to the escrow

Webhooks

Register an HTTPS endpoint and choose the events you want. The response includes a signing secret (whsec_...) shown once. Each delivery is a POST with X-Webhook-Timestamp and X-Webhook-Signature: sha256=..., where the signature is HMAC-SHA256 over ${timestamp}.${rawBody}.

escrow.createdescrow.fundedescrow.milestone.release_requestedescrow.milestone.approvedescrow.completedescrow.disputedescrow.resolvedescrow.refunded
POST /v1/partner/webhooks
curl -X POST https://api-staging.saharix.com/v1/partner/webhooks \
  -H "Authorization: Bearer sk_test_9f2c1a40.<secret>" \
  -H "x-idempotency-key: 1b4e28ba-2fa1-11d2-883f-0016d3cca427" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://your-app.com/hooks/saharix",
        "events": ["escrow.funded", "escrow.completed"] }'

# 201 Created
# { "status": "success", "data": {
#     "id": "e2c1...", "url": "https://your-app.com/hooks/saharix",
#     "events": ["escrow.funded", "escrow.completed"],
#     "is_active": true, "secret": "whsec_..." } }
Verify a delivery (Node.js)
const expected = crypto
  .createHmac("sha256", webhookSecret)
  .update(`${timestamp}.${rawBody}`)
  .digest("hex");
const received = (req.headers["x-webhook-signature"] || "").replace("sha256=", "");
if (!crypto.timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(received, "hex"))) {
  throw new Error("Invalid webhook signature");
}
Delivery payload
{ "event": "escrow.funded",
  "created_at": "2026-10-02T12:00:00.000Z",
  "data": { "escrow_id": "0b6f...", "partner_ref": null,
            "status": "FUNDED", "title": "Website redesign",
            "amount": 45000000, "currency": "NGN",
            "milestone_id": null } }

Failed deliveries retry up to 5 times with backoff (1m, 5m, 15m, 60m, 4h). Endpoints must be public HTTPS addresses; private, loopback, and link-local hosts are rejected. List endpoints with GET /v1/partner/webhooks and disable one with DELETE /v1/partner/webhooks/:id.

Errors

Errors use one envelope, with a stable machine-readable code:

Error response
{ "status": "error",
  "code": "validation_error",
  "message": "Invalid request data",
  "details": [] }

401 means a missing, malformed, or revoked key. 403 means the key lacks the scope for that endpoint. 404 means the escrow does not belong to your workspace. Throttled and abusive traffic can receive 429; retry after a short pause.

Ready to embed escrow?

Request a workspace and we will set you up with sandbox keys, guidance, and a direct line to the team.