Webhooks
Webhooks allow Galatext to send real-time notifications to your application when events occur, such as message delivery updates or incoming messages.
Why Use Webhooks?
- Real-time updates — Get notified instantly instead of polling the API
- Event-driven architecture — Trigger your business logic on message events
- Reduced latency — Know the moment a message is delivered or fails
- Two-way messaging — Receive incoming SMS or WhatsApp replies
Supported Events
| Event | Description |
|---|---|
message.pending | Message accepted and queued |
message.sent | Message submitted to carrier |
message.delivered | Message delivered to recipient |
message.failed | Message delivery failed |
message.expired | Message expired before delivery |
message.incoming | Incoming SMS received (two-way) |
whatsapp.incoming | Incoming WhatsApp message received |
Setup Guide
Step 1: Create Your Endpoint
Build an HTTPS endpoint in your application that can receive POST requests:
// Express.js example
const express = require('express');
const app = express();
app.post('/webhooks/galatext', express.json(), (req, res) => {
const event = req.body;
console.log('Received webhook event:', event.event);
// Handle different event types
switch (event.event) {
case 'message.delivered':
// Update your database
console.log(`Message ${event.data.messageId} delivered`);
break;
case 'message.failed':
console.log(`Message ${event.data.messageId} failed:`, event.data.reason);
break;
case 'message.incoming':
console.log(`Incoming SMS from ${event.data.from}: ${event.data.text}`);
break;
}
// Acknowledge receipt - very important!
res.status(200).json({ received: true });
});
app.listen(3000);# Flask example
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/webhooks/galatext', methods=['POST'])
def handle_webhook():
event = request.json
print(f"Received event: {event['event']}")
if event['event'] == 'message.delivered':
msg_id = event['data']['messageId']
print(f"Message {msg_id} delivered")
# Return 200 to acknowledge
return jsonify({"received": True}), 200Step 2: Configure in Dashboard
- Log in to the Galatext Dashboard
- Navigate to Settings → Webhooks
- Click Add Webhook
- Enter your endpoint URL (must be HTTPS)
- Select events to subscribe to
- Optionally set a Secret for signature verification
- Click Save
Step 3: Test Your Webhook
Use the Send Test button in the dashboard to send a sample payload to your endpoint.
Payload Formats
Delivery Report Webhook
{
"event": "message.delivered",
"timestamp": "2025-06-15T14:32:10Z",
"data": {
"messageId": "msg_abc123def456",
"batchId": "batch_xyz789abc012",
"phoneNumber": "+254712345678",
"status": "DELIVERED",
"senderId": "YOURBRAND",
"reference": "order-12345",
"deliveredAt": "2025-06-15T14:32:10Z",
"creditsUsed": 1
}
}Incoming SMS Webhook
{
"event": "message.incoming",
"timestamp": "2025-06-15T15:00:00Z",
"data": {
"messageId": "inc_msg_001",
"from": "+254712345678",
"to": "GALATEXT",
"text": "STOP",
"receivedAt": "2025-06-15T15:00:00Z"
}
}Incoming WhatsApp Webhook
{
"event": "whatsapp.incoming",
"timestamp": "2025-06-15T16:00:00Z",
"data": {
"messageId": "wain_001",
"from": "+254712345678",
"type": "text",
"text": {
"body": "Hello, I need help with my order"
},
"timestamp": "2025-06-15T16:00:00Z"
}
}Security & Verification
Galatext signs each webhook payload using HMAC-SHA256 with your webhook secret.
Verifying Signatures
The signature is sent in the X-Galatext-Signature header:
X-Galatext-Signature: t=1718461200,v1=abc123def456...The payload format for signature calculation is: {timestamp}.{body} where body is the raw JSON request body.
JavaScript Verification
const crypto = require('crypto');
function verifySignature(req, secret) {
const signature = req.headers['x-galatext-signature'];
if (!signature) return false;
const parts = signature.split(',');
const timestamp = parts[0].split('=')[1];
const receivedSig = parts[1].split('=')[1];
// 5-minute tolerance for clock skew
const now = Math.floor(Date.now() / 1000);
if (now - parseInt(timestamp) > 300) return false;
const payload = `${timestamp}.${JSON.stringify(req.body)}`;
const expectedSig = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(receivedSig),
Buffer.from(expectedSig)
);
}Python Verification
import hmac
import hashlib
import json
from flask import request
def verify_webhook(secret):
signature = request.headers.get('X-Galatext-Signature')
if not signature:
return False
parts = signature.split(',')
timestamp = parts[0].split('=')[1]
received_sig = parts[1].split('=')[1]
# 5-minute tolerance
import time
if time.time() - int(timestamp) > 300:
return False
payload = f"{timestamp}.{json.dumps(request.json, separators=(',', ':'))}"
expected_sig = hmac.new(
secret.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(received_sig, expected_sig)IP Ranges
Add the following IP ranges to your allowlist for Galatext webhook requests:
103.xxx.xxx.0/24
167.xxx.xxx.0/24Contact support for the current list of webhook source IPs.
Retry Policy
If your endpoint does not return a 200 OK status within 5 seconds, Galatext retries with exponential backoff:
| Attempt | Delay |
|---|---|
| 1st | Immediate |
| 2nd | 30 seconds |
| 3rd | 2 minutes |
| 4th | 10 minutes |
| 5th | 30 minutes |
| 6th | 2 hours |
After 6 failed attempts, the event is discarded. You can view failed webhooks in the dashboard under Settings → Webhooks → Logs.
Best Practices
- Respond quickly — Your endpoint should return
200 OKwithin 5 seconds - Process asynchronously — Queue the event for processing and respond immediately
- Make it idempotent — Handle duplicate webhook deliveries gracefully
- Verify signatures — Always validate the HMAC signature
- Use HTTPS only — Never use HTTP for webhook endpoints
- Log everything — Keep audit logs of all webhook events
- Set a webhook secret — This enables signature verification