Salami Gateway handles delivery reports (DLRs) from SMS providers and forwards status updates to your application via configurable webhooks.
Providers send delivery reports and status updates to this endpoint:
ANY /api/sms/apps/{sms_app}/callback
| Parameter | Type | Description |
|---|---|---|
sms_app |
integer | SMS App ID |
No Bearer token required. This endpoint is called directly by SMS providers.
https://your-domain.com/api/sms/apps/{app_id}/callback
Message record.| Code | Description |
|---|---|
200 |
Callback processed |
404 |
SMS App not found |
500 |
Processing error |
Each provider requires you to set a callback URL. Examples:
Set the "Status Callback URL" on your Twilio phone number or messaging service:
https://your-domain.com/api/sms/apps/{app_id}/callback
Set the delivery reports callback URL in the dashboard:
https://your-domain.com/api/sms/apps/{app_id}/callback
Set the DLR webhook in your Vonage application settings:
https://your-domain.com/api/sms/apps/{app_id}/callback
Configure the notify URL when sending messages or in your Infobip account settings:
https://your-domain.com/api/sms/apps/{app_id}/callback
Set the status report URL in your MessageBird dashboard:
https://your-domain.com/api/sms/apps/{app_id}/callback
Webhooks forward message events from Salami to your application. Configure webhooks through the web dashboard under SMS > Apps > [Your App] > Webhooks.
| 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 | Message field to match against |
comparator |
string | Yes | Comparison operator |
keyword |
string | Yes | Value to compare against |
Webhooks support conditional filtering. Only messages matching your criteria trigger the webhook.
Examples:
Fire on all incoming messages:
field: folder, comparator: equals, keyword: inboxFire on all delivered messages:
field: status, comparator: equals, keyword: deliveredFire on messages containing a keyword:
field: message, comparator: contains, keyword: SUBSCRIBEFire on messages from a specific number:
field: from, comparator: equals, keyword: 254712345678| 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 |
| Field | Description |
|---|---|
to |
Recipient phone number |
from |
Sender phone number |
message |
Message body text |
status |
Message status (pending, sent, delivered, failed) |
folder |
inbox or outbox |
channel |
sms, mms, voice, whatsapp |
sms_provider |
Provider driver name |
cost |
Message cost |
telco_name |
Carrier name |
When a webhook fires, Salami sends a POST request to your callback_url.
| Header | Description |
|---|---|
User-Agent |
Salami-Webhooks/v1.0 |
Content-Type |
application/json |
X-SALAMI-APP-ID |
SMS App ID |
X-SALAMI-ACCOUNT-ID |
Account/User ID of the app owner |
X-SALAMI-SIGNATURE |
HMAC signature (if webhook secret is configured) |
{
"id": 156,
"to": "254712345678",
"from": "+15551234567",
"message": "Hello! Your order #1234 has been confirmed.",
"sms_provider": "Twilio",
"provider_id": "SM1234567890abcdef",
"message_time": "2026-03-15T14:30:00.000000Z",
"telco_code": "63902",
"telco_name": "Safaricom",
"message_parts": 1,
"retry_count": 0,
"status": "delivered",
"delivery_date": "2026-03-15T14:30:05.000000Z",
"cost": "0.0075",
"folder": "outbox",
"channel": "sms",
"app_id": 1,
"created_by": 1,
"created_at": "2026-03-15T14:30:00.000000Z",
"updated_at": "2026-03-15T14:30:05.000000Z"
}
For voice-enabled apps, call events also trigger webhooks:
{
"id": 1,
"from": "254712345678",
"to": "+15551234567",
"duration": 45,
"status": "completed",
"app_id": 5,
"created_at": "2026-03-15T16:00:00.000000Z"
}
If you configure a webhook_secret, verify the X-SALAMI-SIGNATURE header.
X-SALAMI-SIGNATURE header.$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)) {
// Valid -- process the webhook
} else {
// Invalid -- reject
http_response_code(401);
exit;
}
const crypto = require('crypto');
function verifySignature(payload, signature, secret) {
const computed = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(computed),
Buffer.from(signature)
);
}
Failed webhook dispatches 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 job is deleted from the queue |
A webhook dispatch is considered failed if:
All dispatch attempts are logged and viewable in the dashboard under SMS > Apps > [Your App] > Webhook Logs. Each log entry includes:
In addition to webhooks, configure Slack notifications for real-time alerts under SMS > Apps > [Your App] > Slack Integrations.
Slack notifications are triggered for:
Message Created (status: pending)
|
v
Provider Accepts (status: sent)
|
v
DLR Arrives at /api/sms/apps/{app}/callback
|
v
Driver Parses DLR --> Updates Message (status: delivered/failed)
|
v
Webhooks Dispatched to Your Endpoints
|
v
Slack Notifications Sent