Salami Gateway

API Documentation
Back to Dashboard

Async Request Pattern

All mutating API endpoints in Salami use an asynchronous request pattern. Instead of blocking until the external API responds, Salami immediately accepts your request and processes it in the background.

Flow

1. You send a POST request
2. Salami returns 202 Accepted with a request_id
3. Background worker processes the request (calls Safaricom, QB, etc.)
4. Result delivered via webhook to your callback_url
5. You can also poll GET /api/requests/{request_id} anytime

Making a Request

Include callback_url and webhook_secret in your request body to receive webhook notifications:

POST /api/pay/{app}/requestPayment
{
    "phone": "254700000000",
    "amount": 100,
    "callback_url": "https://your-app.com/webhooks/salami",
    "webhook_secret": "your-hmac-secret"
}

Response (202 Accepted)

{
    "request_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "queued",
    "poll_url": "https://salami.dgl.co.ke/api/requests/550e8400-e29b-41d4-a716-446655440000"
}

Polling Status

GET /api/requests/{request_id}
Authorization: Bearer {token}

Response

{
    "request_id": "550e8400-e29b-41d4-a716-446655440000",
    "service": "payments",
    "action": "request_payment",
    "status": "completed",
    "created_at": "2026-06-04T10:00:00+00:00",
    "completed_at": "2026-06-04T10:00:03+00:00",
    "result": { "transaction_id": "TXN123", "receipt": "ABC..." },
    "error": null
}

Status Values

Status Meaning
queued Request accepted, waiting for worker
processing Worker is calling the external API
completed Success — check result field
failed Error — check error field

Webhook Delivery

All Salami webhooks use a unified envelope format — whether triggered by an async API request, a provider event (payment received, SMS delivered), or a system event (lock accessed, attendance scan).

POST https://your-app.com/webhooks/salami
Content-Type: application/json
X-Salami-Signature: {hmac-sha256-signature}

{
    "event": "payments.request_payment",
    "request_id": "550e8400-e29b-41d4-a716-446655440000",
    "service": "payments",
    "action": "request_payment",
    "status": "completed",
    "data": { ... },
    "error": null,
    "timestamp": "2026-06-05T10:00:03+00:00"
}

Envelope Fields

Field Type Description
event string Dot-notation event name (e.g. payments.transaction.completed, kra.sale.sync, locks.accessed)
request_id string/null UUID of the async API request (null for provider-initiated events like payment confirmations)
service string payments, sms, quickbooks, kra, locks, attendance
action string What happened (e.g. request_payment, transaction.completed, sale.sync)
status string completed or failed
data object Service-specific payload (transaction details, scan data, etc.)
error string/null Error message if status is failed
timestamp string ISO 8601 timestamp

Event Types

Event Trigger
payments.request_payment Async payment request completed
payments.transaction.completed Provider confirmed a payment
sms.send Async SMS send completed
sms.message.delivered SMS delivery report received
quickbooks.sync_contact QB contact sync completed
kra.sale.sync eTIMS sale confirmed by KRA
locks.accessed Lock was unlocked/accessed
attendance.scans.received Biometric scans batch received

Two Types of Webhooks

For some services, you may receive two webhooks for a single operation:

  1. Request callback (request_id present) — your API request was processed
  2. Provider event (request_id is null) — the external provider confirmed the result

Example for payments:

POST /api/pay/{app}/requestPayment  →  202 Accepted
    ↓
Webhook 1: { event: "payments.request_payment", request_id: "abc", status: "completed", data: { checkout_request_id: "..." } }
    ↓ (user enters M-Pesa PIN)
Webhook 2: { event: "payments.transaction.completed", request_id: null, data: { id: 1, amount: 3200, payer_name: "RUTH", ... } }

Use request_id to distinguish: if present, it's the async request result. If null, it's a provider-initiated event.

Verifying Signatures

The X-Salami-Signature header contains an HMAC-SHA256 hash of the JSON payload, signed with your webhook_secret:

$payload = file_get_contents('php://input');
$expected = hash_hmac('sha256', $payload, $yourWebhookSecret);
$received = $request->header('X-Salami-Signature');

if (!hash_equals($expected, $received)) {
    abort(401, 'Invalid signature');
}

Retry Policy

Webhooks retry up to 6 times with exponential backoff (5s, 10s, 30s, 60s, 120s, 300s) if your endpoint returns a non-2xx response.

Affected Endpoints

Payments (/api/pay/)

SMS (/api/sms/)

QuickBooks (/api/quickbooks/)

Locks (/api/locks/)

KRA eTIMS (/api/etims/)

Already async — no changes. Uses same pattern with callback_url.

Queue Infrastructure

For production deployments, use Redis-backed queues:

QUEUE_CONNECTION=redis

Run dedicated workers:

# External API calls (payments, SMS, QB, locks)
php artisan queue:work redis --queue=external-api --tries=3 --backoff=5,30,60

# Webhook delivery to consumers
php artisan queue:work redis --queue=webhooks --tries=6 --backoff=5,10,30,60,120,300

Need help? Contact us at support@dgl.co.ke
© 2026 Deadan Group Limited. All rights reserved.
⚡ API Explorer
LIVE
// Response will appear here...