Configure webhooks to receive real-time notifications when events occur in your Salami Gateway. Webhooks push data to your server when payments are received, SMS messages arrive, or other events trigger.
Webhooks provide push-based notifications instead of polling. When a configured event occurs, Salami sends an HTTP POST request to your specified URL with the event data.
| Type | Module | Events |
|---|---|---|
| Payment Webhook | Payments | Transaction received, status changed, reversal |
| SMS Webhook | SMS | Message received, delivery report |
Event occurs (e.g., payment received)
|
v
Salami checks webhook rules (keyword, field, comparator)
|
v
Match found? --> POST to callback_url with payload
|
v
Your server processes the webhook
|
v
Returns 200 OK (or webhook is retried)
Payment webhooks notify your server when transactions occur on your payment apps.
| Field | Type | Description |
|---|---|---|
id |
integer | Webhook rule ID |
keyword |
string | Value to match against |
comparator |
string | How to match (equals, contains, starts_with, regex) |
field |
string | Transaction field to check |
callback_url |
string | Your endpoint URL |
app_id |
integer | Payment app ID |
webhook_secret |
string | Secret for signature verification |
| Comparator | Description | Example |
|---|---|---|
equals |
Exact match | field: "status", keyword: "STATUS_COMPLETED" |
contains |
Substring match | field: "account", keyword: "INV-" |
starts_with |
Prefix match | field: "phone", keyword: "2547" |
regex |
Regular expression | field: "account", keyword: "^INV-\\d{4}$" |
* |
Match all events | Triggers on every transaction |
| Field | Description | Example Values |
|---|---|---|
status |
Transaction status | STATUS_COMPLETED, STATUS_FAILED |
phone |
Payer phone number | 254712345678 |
account |
Account/reference | INV-2026-001 |
amount |
Transaction amount | 1500.00 |
transaction_id |
Provider transaction ID | ABC123XYZ |
type |
Transaction type | C2B, B2C, B2B |
{
"field": "status",
"comparator": "equals",
"keyword": "STATUS_COMPLETED",
"callback_url": "https://yourserver.com/webhooks/payment",
"webhook_secret": "whsec_abc123def456"
}
{
"field": "account",
"comparator": "starts_with",
"keyword": "SCHOOL-",
"callback_url": "https://yourserver.com/webhooks/school-payments",
"webhook_secret": "whsec_school123"
}
{
"event": "transaction.completed",
"timestamp": "2026-03-29T18:30:00Z",
"webhook_id": 5,
"data": {
"id": 12345,
"transaction_id": "ABC123XYZ",
"reference": "INV-2026-001",
"amount": 1500.00,
"currency": "KES",
"payer_account": "254712345678",
"payer_name": "John Doe",
"payee_account": "174379",
"payee_name": "My Business",
"status": "STATUS_COMPLETED",
"transaction_type": "C2B",
"payment_provider": "MpesaKeC2B",
"transaction_time": "2026-03-29T18:30:00Z",
"narration": "Payment for Invoice INV-2026-001"
},
"signature": "sha256=abc123def456..."
}
SMS webhooks notify your server when messages are received or delivery reports arrive.
| Field | Type | Description |
|---|---|---|
id |
integer | Webhook rule ID |
keyword |
string | Value to match against |
comparator |
string | How to match |
field |
string | Message field to check |
callback_url |
string | Your endpoint URL |
app_id |
integer | SMS app ID |
webhook_secret |
string | Secret for signature verification |
| Field | Description | Example Values |
|---|---|---|
from |
Sender phone number | 254712345678 |
message |
Message content | BALANCE, SUBSCRIBE |
status |
Delivery status | delivered, failed |
direction |
Message direction | inbound, outbound |
{
"field": "direction",
"comparator": "equals",
"keyword": "inbound",
"callback_url": "https://yourserver.com/webhooks/sms",
"webhook_secret": "whsec_sms456"
}
{
"field": "message",
"comparator": "starts_with",
"keyword": "BALANCE",
"callback_url": "https://yourserver.com/webhooks/balance-check",
"webhook_secret": "whsec_balance789"
}
Incoming Message:
{
"event": "message.received",
"timestamp": "2026-03-29T18:35:00Z",
"webhook_id": 8,
"data": {
"id": 501,
"sms_app_id": 1,
"from": "+254712345678",
"to": "+254798765432",
"message": "BALANCE check my account",
"direction": "inbound",
"received_at": "2026-03-29T18:35:00Z"
},
"signature": "sha256=xyz789abc012..."
}
Delivery Report:
{
"event": "message.delivered",
"timestamp": "2026-03-29T18:36:00Z",
"webhook_id": 9,
"data": {
"id": 12345,
"sms_app_id": 1,
"to": "+254712345678",
"status": "delivered",
"delivered_at": "2026-03-29T18:35:30Z",
"parts": 1,
"cost": 0.80
},
"signature": "sha256=def456ghi789..."
}
Webhooks are configured through the Salami dashboard:
| Field | Required | Description |
|---|---|---|
| Field | Yes | The data field to match against |
| Comparator | Yes | Match type (equals, contains, starts_with, regex, *) |
| Keyword | Yes | Value to match (use * to match everything) |
| Callback URL | Yes | Your HTTPS endpoint |
| Webhook Secret | No | Secret for payload signing (recommended) |
All webhook payloads follow this structure:
{
"event": "event.type",
"timestamp": "ISO 8601 timestamp",
"webhook_id": 5,
"data": {
// Event-specific data
},
"signature": "sha256=..."
}
| Event | Module | Description |
|---|---|---|
transaction.completed |
Payments | Payment completed |
transaction.failed |
Payments | Payment failed |
transaction.reversed |
Payments | Payment reversed |
transaction.pending |
Payments | Payment initiated |
message.received |
SMS | Incoming SMS received |
message.delivered |
SMS | Outbound SMS delivered |
message.failed |
SMS | SMS delivery failed |
Each webhook can have a webhook_secret used to sign payloads. Verify signatures to ensure webhooks are genuinely from Salami.
Salami signs webhook payloads using HMAC-SHA256:
signature = HMAC-SHA256(webhook_secret, request_body)
The signature is included in:
signature field in the JSON payloadX-Webhook-Signature HTTP header<?php
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$secret = 'your_webhook_secret';
$expectedSignature = 'sha256=' . hash_hmac('sha256', $payload, $secret);
if (!hash_equals($expectedSignature, $signature)) {
http_response_code(401);
echo json_encode(['error' => 'Invalid signature']);
exit;
}
// Signature valid -- process the webhook
$data = json_decode($payload, true);
const crypto = require('crypto');
app.post('/webhooks/payment', (req, res) => {
const payload = JSON.stringify(req.body);
const signature = req.headers['x-webhook-signature'];
const secret = 'your_webhook_secret';
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
if (signature !== expected) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process webhook
const event = req.body.event;
const data = req.body.data;
res.status(200).json({ received: true });
});
import hmac
import hashlib
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/webhooks/payment', methods=['POST'])
def webhook():
payload = request.get_data()
signature = request.headers.get('X-Webhook-Signature', '')
secret = b'your_webhook_secret'
expected = 'sha256=' + hmac.new(secret, payload, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, signature):
return jsonify({'error': 'Invalid signature'}), 401
data = request.get_json()
# Process webhook
return jsonify({'received': True}), 200
webhook_id + timestamp as a dedup key)When your server does not return a 2xx response, Salami retries the webhook:
| Attempt | Delay | Total Time |
|---|---|---|
| 1 | Immediate | 0 minutes |
| 2 | 1 minute | 1 minute |
| 3 | 5 minutes | 6 minutes |
| 4 | 30 minutes | 36 minutes |
| 5 | 2 hours | ~2.5 hours |
| 6 | 12 hours | ~14.5 hours |
| 7 (final) | 24 hours | ~38.5 hours |
| Your Response | Salami Action |
|---|---|
200-299 |
Success, no retry |
400-499 |
Client error, no retry (except 408, 429) |
408 (Timeout) |
Retries with backoff |
429 (Rate Limited) |
Retries with backoff |
500-599 |
Server error, retries with backoff |
| Connection refused | Retries with backoff |
| Timeout (30s) | Retries with backoff |
Your webhook endpoint should return:
| Code | Meaning | Salami Action |
|---|---|---|
200 |
Success | Webhook marked as delivered |
201 |
Created | Webhook marked as delivered |
204 |
No Content | Webhook marked as delivered |
400 |
Bad Request | No retry (permanent failure) |
401 |
Unauthorized | No retry |
500 |
Server Error | Retry with backoff |
503 |
Unavailable | Retry with backoff |
View webhook delivery history in the Salami dashboard:
Logs include:
Back to: Account Tokens | Payments API