Salami Gateway

API Documentation
Back to Dashboard

Payment Callbacks

Salami Gateway processes payment provider notifications (IPNs/callbacks) and forwards them to your application via configurable webhooks. This document covers how provider callbacks are received, how to configure your own webhooks, and the payload formats.

Provider Callback Endpoint

Payment providers send notifications to Salami via this endpoint:

ANY /api/pay/{payment_app}/callback/{action?}

Path Parameters

Parameter Type Required Description
payment_app integer Yes Payment App ID
action string No Optional action qualifier (provider-specific)

Authentication

This endpoint does not require Bearer token authentication. It is open to receive notifications directly from payment providers.

How It Works

  1. The payment provider sends a POST (or GET) request to the callback URL.
  2. Salami logs the raw IPN request (payload, referrer IP, IP address) in the payment_ipn_requests table.
  3. Slack notifications are sent to any configured Slack integrations.
  4. The provider-specific driver parses the callback and creates/updates a Transaction record.
  5. A TransactionCompleted event fires, triggering your configured webhooks, Slack notifications, and auto-responses.

Callback URL Format

When registering your callback URL with a payment provider, use:

https://your-domain.com/api/pay/{payment_app_id}/callback

For providers that require action-specific URLs:

https://your-domain.com/api/pay/{payment_app_id}/callback/confirmation
https://your-domain.com/api/pay/{payment_app_id}/callback/validation
https://your-domain.com/api/pay/{payment_app_id}/callback/timeout
https://your-domain.com/api/pay/{payment_app_id}/callback/result

Transaction Validation Endpoint

For providers that support transaction validation (e.g., M-Pesa C2B), a separate validation endpoint is available:

ANY /api/pay/{payment_app}/validateTransaction

Authentication

No Bearer token required. Called directly by the payment provider.

How It Works

  1. The provider sends a validation request before completing a C2B transaction.
  2. The driver's validateTransaction method determines whether to accept or reject the payment.
  3. The response is sent back to the provider.

Response Format (M-Pesa C2B)

Accept:

{
  "ResultCode": 0,
  "ResultDesc": "Accepted"
}

Reject:

{
  "ResultCode": 1,
  "ResultDesc": "Rejected"
}

Configuring Your Webhooks

Webhooks allow Salami to forward transaction events to your application. Configure webhooks through the web dashboard under Payments > Apps > [Your App] > Webhooks.

Webhook Fields

Field Type Required Description
callback_url string Yes Your HTTPS endpoint to receive webhook POSTs
webhook_secret string No Secret key for verifying webhook signatures
field string Yes Transaction field to match against (e.g., status, transaction_type, reference)
comparator string Yes Comparison operator (e.g., equals, contains, starts_with)
keyword string Yes Value to compare against

Webhook Filtering

Webhooks support conditional filtering. Only transactions matching your criteria will trigger the webhook.

Examples:

Available Comparators

Comparator Description
equals Exact match
contains Field contains the keyword
starts_with Field starts with the keyword
ends_with Field ends with the keyword
not_equals Field does not equal the keyword

Available Transaction Fields for Matching

Field Description
status Transaction status (STATUS_COMPLETED, STATUS_FAILED, etc.)
transaction_type PAYMENT_IN, PAYMENT_OUT, PAYMENT_NEUTRAL
payer_account Payer's phone/account number
payee_account Payee's phone/account number
reference Payment reference
narration Transaction narration
amount Transaction amount
currency Currency code
payment_provider Driver type identifier

Webhook Payload Format

When a webhook fires, Salami sends a POST request to your callback_url with the transaction data.

Headers

Header Description
User-Agent Salami-Webhooks/v1.0
Content-Type application/json
X-SALAMI-APP-ID Payment App ID
X-SALAMI-ACCOUNT-ID Account/User ID of the app owner
X-SALAMI-SIGNATURE HMAC signature of the payload (if webhook secret is configured)

Body

{
  "id": 42,
  "payer_account": "254712345678",
  "payer_name": "JOHN DOE",
  "payee_account": "174379",
  "payee_name": "My Business",
  "reference": "INV-2026-001",
  "transaction_id": "QJI3R7GXWV",
  "amount": "100.00",
  "currency": "KES",
  "transaction_time": "2026-03-15T14:30:00.000000Z",
  "narration": "Payment for Order #001",
  "status": "STATUS_COMPLETED",
  "transaction_type": "PAYMENT_IN",
  "transaction_sub_type": "CustomerPayBillOnline",
  "payment_provider": "MpesaKeExpress",
  "app_id": 1,
  "created_at": "2026-03-15T14:30:00.000000Z",
  "updated_at": "2026-03-15T14:30:05.000000Z"
}

Verifying Webhook Signatures

If you configure a webhook_secret, Salami signs each webhook request. Verify the signature to ensure the request is authentic.

Verification Steps

  1. Read the X-SALAMI-SIGNATURE header from the incoming request.
  2. Compute an HMAC-SHA256 hash of the raw JSON request body using your webhook secret as the key.
  3. Compare your computed hash with the signature header value.

Example (PHP)

$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_SALAMI_SIGNATURE'];
$secret = 'your-webhook-secret';

$computed = hash_hmac('sha256', $payload, $secret);

if (hash_equals($computed, $signature)) {
    // Signature valid -- process the webhook
} else {
    // Signature mismatch -- reject the request
    http_response_code(401);
    exit;
}

Retry Behavior

Webhooks that fail (network errors, non-2xx responses) are automatically retried.

Behavior Value
Retry delay 5 seconds between attempts
Max attempts Configurable via salami.callbacks.max_attempts
Failure handling After exhausting retries, the webhook is deleted from the queue

Webhook Logs

Every webhook dispatch attempt is logged in the webhook_logs table and viewable in the dashboard under Payments > Apps > [Your App] > Webhook Logs. Logs include:


Provider Callback Payload Formats

M-Pesa Express (STK Push) Callback

{
  "Body": {
    "stkCallback": {
      "MerchantRequestID": "29115-34620561-1",
      "CheckoutRequestID": "ws_CO_191220191020363925",
      "ResultCode": 0,
      "ResultDesc": "The service request is processed successfully.",
      "CallbackMetadata": {
        "Item": [
          { "Name": "Amount", "Value": 100.00 },
          { "Name": "MpesaReceiptNumber", "Value": "QJI3R7GXWV" },
          { "Name": "Balance" },
          { "Name": "TransactionDate", "Value": 20260315143000 },
          { "Name": "PhoneNumber", "Value": 254712345678 }
        ]
      }
    }
  }
}

M-Pesa C2B Confirmation Callback

{
  "TransactionType": "Pay Bill",
  "TransID": "QJI3R7GXWV",
  "TransTime": "20260315143000",
  "TransAmount": "100.00",
  "BusinessShortCode": "174379",
  "BillRefNumber": "INV-2026-001",
  "InvoiceNumber": "",
  "OrgAccountBalance": "1500.00",
  "ThirdPartyTransID": "",
  "MSISDN": "254712345678",
  "FirstName": "JOHN",
  "MiddleName": "",
  "LastName": "DOE"
}

M-Pesa B2C Result Callback

{
  "Result": {
    "ResultType": 0,
    "ResultCode": 0,
    "ResultDesc": "The service request is processed successfully.",
    "OriginatorConversationID": "16740-34861180-1",
    "ConversationID": "AG_20191219_00004492b1b6f0af4f53",
    "TransactionID": "QJI3R7GXWV",
    "ResultParameters": {
      "ResultParameter": [
        { "Key": "TransactionAmount", "Value": 500 },
        { "Key": "TransactionReceipt", "Value": "QJI3R7GXWV" },
        { "Key": "ReceiverPartyPublicName", "Value": "254712345678 - JOHN DOE" },
        { "Key": "TransactionCompletedDateTime", "Value": "15.03.2026 14:30:00" },
        { "Key": "B2CUtilityAccountAvailableFunds", "Value": 50000.00 },
        { "Key": "B2CWorkingAccountAvailableFunds", "Value": 100000.00 },
        { "Key": "B2CRecipientIsRegisteredCustomer", "Value": "Y" },
        { "Key": "B2CChargesPaidAccountAvailableFunds", "Value": 0.00 }
      ]
    }
  }
}

PayPal IPN Callback

mc_gross=100.00&protection_eligibility=Eligible&payer_id=BUYER123&tax=0.00&payment_date=14:30:00+Mar+15,+2026+PDT&payment_status=Completed&mc_currency=USD&receiver_email=seller@example.com&payer_email=buyer@example.com&txn_id=ABC123DEF456

Kopokopo Callback

{
  "topic": "buygoods_transaction_received",
  "id": "cac95329-9fa5-42ca-b589-1f3e163c06f0",
  "created_at": "2026-03-15T14:30:00.000Z",
  "event": {
    "type": "Buygoods Transaction",
    "resource": {
      "id": "cac95329-9fa5-42ca-b589-1f3e163c06f0",
      "amount": "100.00",
      "currency": "KES",
      "till_number": "K000001",
      "system": "Lipa Na M-PESA",
      "status": "Received",
      "sender_phone_number": "+254712345678",
      "sender_first_name": "JOHN",
      "sender_last_name": "DOE"
    }
  }
}

Slack Integrations

In addition to webhooks, you can configure Slack notifications for real-time alerts. Configure Slack webhook URLs under Payments > Apps > [Your App] > Slack Integrations.

Slack notifications are sent for:

Auto-Responses

Auto-responses automatically send a message (e.g., SMS) to the payer when a transaction is completed. Configure under Payments > Apps > [Your App] > Auto-Responses.

Related Documentation


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