Webhooks
Configure webhooks to receive real-time delivery status updates for your messages.
Overview
Webhooks allow you to receive HTTP POST notifications when message events occur, such as delivery confirmations or failures.
Webhook Events
MsGine sends webhooks for the following events:
| Event | Description |
|---|---|
message.sent | Message has been sent to the carrier |
message.delivered | Message was successfully delivered |
message.failed | Message delivery failed |
Setting Up Webhooks
Per-Message Webhooks
Specify a webhook URL when sending a message:
await client.sendSms({
to: '+256701521269',
message: 'Your verification code is 123456',
callbackUrl: 'https://your-app.com/webhooks/msgine'
})Using the REST API:
curl -X POST https://api.msgine.net/api/v1/messages/sms \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"to": "+256701521269",
"message": "Your verification code is 123456",
"callbackUrl": "https://your-app.com/webhooks/msgine"
}'Global Webhooks
Configure a default webhook URL in your account settings:
- Navigate to Developer Settings
- Click Add Webhook
- Enter your webhook URL
- Select the events you want to receive
- Save the configuration
All messages will send events to this URL unless overridden by a per-message callbackUrl.
Webhook Payload
When an event occurs, MsGine sends a POST request to your webhook URL:
{
"id": "msg_1234567890",
"event": "message.delivered",
"status": "delivered",
"to": "+256701521269",
"from": "MyApp",
"timestamp": "2024-01-15T10:30:15Z",
"messageId": "msg_1234567890",
"sid": "SM1234567890abcdef"
}Payload Fields
| Field | Type | Description |
|---|---|---|
id | string | Message identifier |
event | string | Event type (e.g., message.delivered) |
status | string | Current message status |
to | string | Recipient phone number |
from | string | Sender ID |
timestamp | string | Event timestamp (ISO 8601) |
messageId | string | Original message ID |
sid | string | Provider-specific message ID |
failureReason | string | Reason for failure (if status is failed) |
Failed Message Payload
When a message fails, the webhook includes additional failure information:
{
"id": "msg_1234567890",
"event": "message.failed",
"status": "failed",
"to": "+256701521269",
"from": "MyApp",
"timestamp": "2024-01-15T10:30:15Z",
"failureReason": "Invalid phone number",
"errorCode": "invalid_phone_number"
}Implementing a Webhook Handler
Node.js/Express
import express from 'express'
import crypto from 'crypto'
const app = express()
app.use(express.json())
app.post('/webhooks/msgine', (req, res) => {
// Verify webhook signature (recommended)
if (!verifyWebhookSignature(req)) {
return res.status(401).json({ error: 'Invalid signature' })
}
const event = req.body
console.log('Webhook received:', event)
// Handle different event types
switch (event.event) {
case 'message.delivered':
console.log(`Message ${event.id} delivered to ${event.to}`)
// Update database, send notification, etc.
break
case 'message.failed':
console.log(`Message ${event.id} failed: ${event.failureReason}`)
// Handle failure, retry, or notify user
break
case 'message.sent':
console.log(`Message ${event.id} sent to carrier`)
break
}
// Acknowledge receipt
res.status(200).json({ received: true })
})
app.listen(3000)Python/Flask
from flask import Flask, request, jsonify
import hmac
import hashlib
app = Flask(__name__)
@app.route('/webhooks/msgine', methods=['POST'])
def webhook():
# Verify webhook signature (recommended)
if not verify_webhook_signature(request):
return jsonify({'error': 'Invalid signature'}), 401
event = request.json
print(f"Webhook received: {event}")
# Handle different event types
if event['event'] == 'message.delivered':
print(f"Message {event['id']} delivered to {event['to']}")
# Update database, send notification, etc.
elif event['event'] == 'message.failed':
print(f"Message {event['id']} failed: {event['failureReason']}")
# Handle failure, retry, or notify user
elif event['event'] == 'message.sent':
print(f"Message {event['id']} sent to carrier")
# Acknowledge receipt
return jsonify({'received': True}), 200PHP
<?php
// webhook.php
$payload = file_get_contents('php://input');
$event = json_decode($payload, true);
// Verify webhook signature (recommended)
if (!verifyWebhookSignature($_SERVER['HTTP_X_MSGINE_SIGNATURE'], $payload)) {
http_response_code(401);
echo json_encode(['error' => 'Invalid signature']);
exit;
}
// Handle different event types
switch ($event['event']) {
case 'message.delivered':
error_log("Message {$event['id']} delivered to {$event['to']}");
// Update database, send notification, etc.
break;
case 'message.failed':
error_log("Message {$event['id']} failed: {$event['failureReason']}");
// Handle failure, retry, or notify user
break;
case 'message.sent':
error_log("Message {$event['id']} sent to carrier");
break;
}
// Acknowledge receipt
http_response_code(200);
echo json_encode(['received' => true]);
?>Webhook Security
Signature Verification
MsGine signs all webhook requests with a signature in the X-MsGine-Signature header:
import crypto from 'crypto'
function verifyWebhookSignature(req: Request): boolean {
const signature = req.headers['x-msgine-signature']
const webhookSecret = process.env.MSGINE_WEBHOOK_SECRET!
const payload = JSON.stringify(req.body)
const expectedSignature = crypto
.createHmac('sha256', webhookSecret)
.update(payload)
.digest('hex')
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
)
}Your webhook secret is available in the Developer Dashboard.
HTTPS Only
MsGine only sends webhooks to HTTPS URLs. HTTP URLs will be rejected.
IP Whitelisting
For additional security, whitelist MsGine's webhook IP addresses:
52.89.214.238
34.212.75.30
54.218.53.128Webhook Best Practices
1. Respond Quickly
Acknowledge webhooks within 5 seconds:
app.post('/webhooks/msgine', async (req, res) => {
// Immediately acknowledge
res.status(200).json({ received: true })
// Process asynchronously
processWebhookAsync(req.body).catch(console.error)
})2. Handle Duplicates
Webhooks may be delivered multiple times. Use idempotency:
const processedWebhooks = new Set()
app.post('/webhooks/msgine', (req, res) => {
const webhookId = req.body.id
if (processedWebhooks.has(webhookId)) {
console.log('Duplicate webhook, skipping')
return res.status(200).json({ received: true })
}
processedWebhooks.add(webhookId)
// Process webhook
// ...
res.status(200).json({ received: true })
})3. Retry Failed Processing
If processing fails, return an error and MsGine will retry:
app.post('/webhooks/msgine', async (req, res) => {
try {
await processWebhook(req.body)
res.status(200).json({ received: true })
} catch (error) {
console.error('Webhook processing failed:', error)
res.status(500).json({ error: 'Processing failed' })
// MsGine will retry
}
})4. Log Webhooks
Log all webhooks for debugging and audit trails:
app.post('/webhooks/msgine', (req, res) => {
console.log('Webhook received:', {
id: req.body.id,
event: req.body.event,
timestamp: req.body.timestamp
})
// Process and respond
// ...
})Webhook Retries
If your webhook endpoint:
- Returns a non-2xx status code
- Times out (5 second limit)
- Is unreachable
MsGine will retry with exponential backoff:
| Attempt | Delay |
|---|---|
| 1st retry | 1 minute |
| 2nd retry | 5 minutes |
| 3rd retry | 15 minutes |
| 4th retry | 1 hour |
| 5th retry | 6 hours |
After 5 failed attempts, the webhook is marked as failed and you'll receive an email notification.
Testing Webhooks
Local Development
Use a tool like ngrok to expose your local server:
ngrok http 3000Then use the ngrok URL as your webhook URL:
https://abc123.ngrok.io/webhooks/msgineWebhook Testing Tool
Use the webhook testing tool in the Developer Dashboard to send test events.
Troubleshooting
Webhooks Not Received
- Check HTTPS: Ensure your URL uses HTTPS
- Verify URL: Check the URL is publicly accessible
- Check firewall: Ensure webhook IPs aren't blocked
- Review logs: Check your server logs for incoming requests
Signature Verification Fails
- Check secret: Ensure you're using the correct webhook secret
- Raw payload: Use the raw request body, not parsed JSON
- Encoding: Ensure UTF-8 encoding
Webhooks Delayed
Webhooks are usually delivered within seconds, but may be delayed due to:
- Retries from previous failures
- High volume of messages
- Network issues
Next Steps
- SMS API - Send SMS messages
- REST API - Complete API reference
- Authentication - Manage API tokens