Key Takeaways
- An Android SMS gateway server webhook is your HTTPS endpoint receiving signed POSTs for DLR and inbound SMS.
- Verify X-SmsGateway-Signature = v1=hex(HMAC-SHA256("{timestamp}.{body}", secret)). Reject stale timestamps.
- Deduplicate on X-SmsGateway-Event-Id. A retry is the same event, not a license to send again.
- Register URLs via the webhooks API; live event names live in Developer Center.
- You bring phones and airtime. Pricing is devices plus send volume.
Android sms gateway server webhook is the backend half of two-way and DLR: a public HTTPS URL you own, signed POSTs, and a unique constraint on event id. Product page: webhooks. API spoke: API webhook in-depth.
Live contract: Developer Center. You bring the phone and airtime. We meter devices and volume.
POST /hooks/sms
X-SmsGateway-Signature: v1=…
Your server is the subscriber
Register at POST /api/v1/webhooks. The control plane POSTs JSON. The Android does not open a hole in your VPC — you publish a URL the plane can reach.
A 200 you return after enqueueing work is enough. A 500 makes us retry. Retry plus a non-idempotent handler is how you double-text.
HMAC headers
X-SmsGateway-Signature = v1=hex(HMAC-SHA256("{timestamp}.{body}", secret)). Timestamp is X-SmsGateway-Timestamp (unix seconds). Deduplicate on X-SmsGateway-Event-Id. HMAC background: RFC 2104.
Illustrative delivered payload:
{
"id": "evt_01K2F8QW3N4RXB7M",
"type": "message.delivered",
"createdAt": "2026-08-12T14:04:09Z",
"apiVersion": "2026-08-12",
"data": {
"message": {
"id": 41823,
"number": "+14155552671",
"text": "Your verification code is 481920",
"status": "Delivered",
"campaignId": 17,
"deviceId": 3,
"metadata": { "orderId": "1234" },
"sentAt": "2026-08-12T14:04:02Z",
"deliveredAt": "2026-08-12T14:04:09Z"
}
}
}PHP receiver sample
<?php
header('Content-Type: application/json');
$secret = getenv('SMS_GATEWAY_WEBHOOK_SECRET');
$timestamp = $_SERVER['HTTP_X_SMSGATEWAY_TIMESTAMP'] ?? '';
$sigHeader = $_SERVER['HTTP_X_SMSGATEWAY_SIGNATURE'] ?? '';
$eventId = $_SERVER['HTTP_X_SMSGATEWAY_EVENT_ID'] ?? '';
$body = file_get_contents('php://input');
if ($timestamp === '' || $sigHeader === '' || $secret === '') {
http_response_code(400);
echo json_encode(['error' => 'missing_signature']);
exit;
}
if (abs(time() - (int) $timestamp) > 300) {
http_response_code(401);
echo json_encode(['error' => 'stale_timestamp']);
exit;
}
$expected = 'v1=' . hash_hmac('sha256', $timestamp . '.' . $body, $secret);
$ok = false;
foreach (preg_split('/\s+/', $sigHeader) as $candidate) {
if (hash_equals($expected, $candidate)) {
$ok = true;
break;
}
}
if (!$ok) {
http_response_code(401);
echo json_encode(['error' => 'bad_signature']);
exit;
}
// Deduplicate on X-SmsGateway-Event-Id (retries and replays reuse it).
$data = json_decode($body, true) ?: [];
$type = $data['type'] ?? '';
$message = $data['data']['message'] ?? [];
if ($type === 'message.received') {
$from = $message['number'] ?? '';
$text = $message['text'] ?? '';
error_log("Inbound SMS $eventId from $from: $text");
}
http_response_code(200);
echo json_encode(['ok' => true]);
C# pattern lives with C# samples. PHP: PHP samples. Those are REST examples, not a packaged SDK product.
Event handling
| type | Your job | Do not |
|---|---|---|
| message.delivered / failed | Update order/OTP row; unique on event id | POST /messages again from the handler |
| message.received | Parse STOP vs YES/NO with a policy | Treat every inbound as marketing consent |
| Unknown type | 200 + log; do not 500-loop | Crash the worker |
Retries are not new events
Same event id ⇒ same row. If you auto-reply, that outbound send needs its own Idempotency-Key. Idempotent send · duplicate sends.
Public HTTPS, not localhost
ngrok for staging is fine. Production needs a stable cert and a secret in env — never in the APK. API keys in env.
Webhooks are not SMS credit
Callbacks are HTTP. Radio sends still burn operator airtime and platform volume. Free: 300 SMS lifetime. Developer: 25,000/year. Starter/Pro/Business uncap platform volume, devices 2/5/15. Pause on Free/Developer at allowance. No unlimited carrier SMS.
Checklist
- Signature + timestamp window + event-id unique index.
- 200 after persist; heavy work async.
- Inbound STOP vs operational keywords written down.
- Webhook secret rotated like the API key.
Next steps
Point staging at the PHP sample, send one OTP, confirm delivered + received events once each. Two-way SMS.
Related product pages
Jump to the live product docs for this topic—not another long-form article.
- SMS webhook integrationInbound and status events
- device and SMS volume pricingPlans and allowances
- Android SMS gateway product guideDefinition, product, and how to buy
- SMS API documentationLive endpoint reference





