MessageBird SMS Delivery Status Tracking with Node.js, Vite, React & Vue: Complete Webhook Tutorial
Build a production-ready SMS delivery status tracking system with MessageBird webhooks, Node.js backend, and Vite frontend (React/Vue). Step-by-step guide covering delivery callbacks, webhook security, and real-time status updates.
Tracking SMS delivery status is critical for production applications that rely on SMS for authentication, notifications, or transactional messages. This comprehensive tutorial shows you how to build a production-ready MessageBird SMS delivery tracking system with a Node.js backend and Vite-powered frontend (React or Vue), implementing secure webhook handling for real-time delivery status updates.
This guide provides a step-by-step walkthrough for building a complete SMS delivery tracking system with MessageBird's webhook callbacks. You'll learn how to send SMS messages via MessageBird's REST API, configure and secure delivery status webhooks, and display real-time delivery updates in a modern Vite + React or Vue frontend application.
By the end of this guide, you will have:
A Node.js Express backend that sends SMS via MessageBird and receives delivery status webhooks
Secure webhook signature verification to ensure callbacks are authentic
A database schema to track message delivery states (sent, delivered, failed)
A Vite-powered React or Vue frontend displaying real-time delivery status
Production-ready error handling and deployment strategies
Key Technologies:
Node.js: JavaScript runtime for building the backend API and webhook server
Express: Lightweight Node.js web framework for REST endpoints and webhook handlers
MessageBird REST API: Cloud communications platform for sending SMS and receiving delivery status callbacks via webhooks
Vite: Next-generation frontend build tool offering fast development and optimized production builds
React or Vue: Modern JavaScript frameworks for building the delivery status dashboard UI
dotenv: Environment variable management for secure credential storage
crypto: Node.js native module for webhook signature verification
System Architecture:
The system consists of three main components: a frontend dashboard (Vite + React/Vue), a Node.js backend API, and MessageBird's webhook service. When you send an SMS, MessageBird assigns it a unique message ID and begins delivery. As the delivery progresses through states (sent → buffered → delivered or failed), MessageBird sends HTTP POST webhooks to your server with status updates. Your webhook handler verifies the signature, updates the database, and the frontend polls or uses WebSockets to display real-time status.
MessageBird API Account: Sign up at MessageBird. You'll receive test credits for development
MessageBird Access Key: Obtain from your MessageBird Dashboard (Developers → API access)
MessageBird Signing Key: Required for webhook signature verification (Developers → API access → Signing key)
MessageBird Virtual Number: Purchase or configure a number capable of sending SMS
Public Webhook URL: For local development, use ngrok to expose your localhost. For production, use your deployed application URL
(Optional) Database: PostgreSQL, MySQL, or MongoDB for persisting message delivery status
Understanding MessageBird Delivery Status Webhooks
Before diving into implementation, it's essential to understand how MessageBird delivery status webhooks work and why they're critical for production SMS applications.
What Are SMS Delivery Status Webhooks?
When you send an SMS via MessageBird, the API returns a 201 Created response with a message ID almost instantly. However, this doesn't mean the SMS has been delivered to the recipient's phone – it only confirms MessageBird accepted your request. The actual delivery process involves:
Queued/Buffered: MessageBird queues your message for delivery
Sent: The message is handed off to the mobile carrier network
Delivered: The carrier confirms the recipient's device received the message
Failed: Delivery failed due to invalid number, network issues, or carrier rejection
Delivery status webhooks are HTTP POST callbacks that MessageBird sends to your server URL whenever a message's status changes. This enables you to:
Track delivery success rates and identify problematic numbers
Retry failed messages or trigger fallback communication channels
Update users in real-time about message delivery status
Monitor SMS campaign performance and ROI
MessageBird Webhook Payload Structure
MessageBird sends webhooks with a JSON payload containing delivery information. Here's a typical webhook payload for a delivered message:
JSON
{"id":"b65a6c749d7e4758a811ea7c8c7e0000","href":"https://rest.messagebird.com/messages/b65a6c749d7e4758a811ea7c8c7e0000","direction":"mt","type":"sms","originator":"YourBrand","body":"Your verification code is 123456","reference":"customer-order-5678","validity":null,"gateway":10,"typeDetails":{},"datacoding":"plain","mclass":1,"scheduledDatetime":null,"createdDatetime":"2025-10-10T14:30:00+00:00","recipients":{"totalCount":1,"totalSentCount":1,"totalDeliveredCount":1,"totalDeliveryFailedCount":0,"items":[{"recipient":14155552671,"status":"delivered","statusDatetime":"2025-10-10T14:30:15+00:00","messagePartCount":1}]}}
Key Fields for Delivery Tracking:
id: MessageBird's unique message identifier (use this to correlate with your database)
recipients.items[].status: The delivery status (sent, buffered, delivered, delivery_failed, expired)
recipients.items[].statusDatetime: Timestamp when the status was updated
recipients.items[].recipient: The phone number that received (or failed to receive) the message
reference: Your custom reference ID (optional but recommended for linking to orders, users, etc.)
Webhook Security: Signature Verification
Critical: Always verify webhook signatures to prevent malicious actors from sending fake delivery status updates to your system. MessageBird signs each webhook request using HMAC-SHA256 with your signing key.
The signature is sent in the MessageBird-Signature HTTP header. To verify:
Extract the MessageBird-Signature header value
Retrieve the raw request body as a string (before parsing JSON)
Compute HMAC-SHA256 hash of the body using your signing key
Compare your computed signature with the header value
Reject requests with invalid or missing signatures (return 401 Unauthorized)
Example verification code (we'll implement this in detail later):
MessageBird needs a publicly accessible HTTPS URL to send webhooks. For local development, use ngrok:
1. Install and Run ngrok
Bash
# Install ngrok (or download from https://ngrok.com)
npm install -g ngrok
# Start your backend server
node server.js
# In a new terminal, expose port 3000
ngrok http 3000
ngrok will provide a public URL like https://abc123.ngrok.io.
# Check if webhook endpoint is accessible
curl -X POST https://your-domain.com/webhooks/messagebird/delivery \
-H "Content-Type: application/json" \
-d '{"test": true}'# Test locally with ngrok
ngrok http 3000
# Update WEBHOOK_URL in .env and restart server
Signature Verification Failures
Possible causes:
Wrong signing key: Double-check the key from MessageBird dashboard
Request body modified: Body parsers might transform the body; use raw body
Encoding issues: Ensure UTF-8 encoding when computing HMAC
Extract the MessageBird-Signature header, compute HMAC-SHA256 of the raw request body using your signing key, and compare with the header value using crypto.timingSafeEqual() to prevent timing attacks.
What MessageBird delivery statuses should I handle?
Handle these key statuses: sent (handed to carrier), buffered (queued at carrier), delivered (confirmed receipt), delivery_failed (permanent failure), and expired (validity period exceeded).
Can I use Vite with other frameworks besides React and Vue?
Yes, Vite supports Svelte, Preact, Lit, and vanilla JavaScript. Use npm create vite@latest and select your preferred framework template.
How long do MessageBird webhooks take to arrive?
Webhooks typically arrive within 1-30 seconds after status changes. Delivery confirmations depend on carrier speed (usually 5-60 seconds for most carriers).
Do I need a database for production deployments?
Yes, use PostgreSQL, MySQL, or MongoDB to persist message status across server restarts. In-memory storage loses data when the server stops.
How can I test webhooks without deploying to production?
Use ngrok to expose your local development server with a public HTTPS URL. MessageBird will send webhooks to your localhost through the ngrok tunnel.
What happens if my webhook endpoint is down?
MessageBird retries failed webhooks up to 10 times with exponential backoff (up to 24 hours). Ensure your endpoint returns 200 OK quickly to avoid retries.
Can I configure different webhook URLs for different messages?
Yes, use the reportUrl parameter when sending each message. This overrides the default webhook URL configured in your MessageBird dashboard.
How do I handle duplicate webhook deliveries?
Use the message ID as a unique key in your database. When updating status, use UPDATE WHERE messageBirdId = ? which safely handles duplicate webhooks.
What's the difference between MessageBird's signing key and access key?
The access key authenticates API requests you send to MessageBird. The signing key validates webhook requests MessageBird sends to you. Never share either key.
Frequently asked questions
Use the Vonage Messages API with the Express.js framework and the @vonage/server-sdk library. This setup allows your Node.js application to send SMS messages by making HTTP requests to the Vonage API. The article provides a step-by-step guide on setting up this project.
The Vonage Messages API is a versatile tool that enables sending messages over various channels, including SMS. It offers a unified interface for different message types. This article focuses on its SMS capabilities using the @vonage/messages package in Node.js.
Dotenv helps manage environment variables by loading them from a .env file into process.env. This enhances security by keeping sensitive credentials like API keys out of your codebase and makes configuration more manageable.
The Vonage Messages API is suitable when your Node.js applications need features like SMS notifications, two-factor authentication, or other communication services. It's a robust solution for sending SMS messages programmatically.
Create an application in your Vonage API Dashboard, generate a private key file, and link your Vonage virtual number to the application. Then, enable the “Messages” capability within the application settings for SMS functionality.
The private key, along with your Application ID, authenticates your Node.js application with the Vonage API. Keep this file secure and never commit it to version control, as it grants access to your Vonage account resources.
Implement a try...catch block around the vonageMessages.send() function. This allows you to capture and handle potential errors during the API call. Detailed error information is available in the error.response.data property of the error object.
Use E.164 formatting (e.g., 14155552671 or +14155552671) for recipient phone numbers in the 'to' parameter of your API requests to Vonage. Avoid spaces, parentheses, or other formatting.
Yes, Vonage supports international SMS. However, different pricing and regulations may apply based on the destination country. Make sure your account allows international messaging before sending SMS globally.
Use tools like curl or Postman to send POST requests to your local endpoint (e.g., http://localhost:3000/send-sms). The request body should be JSON with 'to' and 'text' fields.
Store API credentials in environment variables, use HTTPS, validate and sanitize user input, implement rate limiting to prevent abuse, and never expose your private key.
Trial Vonage accounts require whitelisting destination numbers. Add the recipient's number to your allowed list in the Vonage Dashboard. You will receive a verification code on the whitelisted number to confirm ownership.
Vonage automatically segments longer messages exceeding the standard SMS character limit (160 for GSM-7, 70 for UCS-2). Consider message length to manage costs.
Choose a platform like Heroku, AWS, or Google Cloud. Configure environment variables securely, and handle the private key by loading its content directly into an environment variable or storing it securely on the server filesystem. Ensure HTTPS for secure communication.
Tracking SMS delivery status is critical for production applications that rely on SMS for authentication, notifications, or transactional messages. This comprehensive tutorial shows you how to build a production-ready MessageBird SMS delivery tracking system with a Node.js backend and Vite-powered frontend (React or Vue), implementing secure webhook handling for real-time delivery status updates.
This guide provides a step-by-step walkthrough for building a complete SMS delivery tracking system with MessageBird's webhook callbacks. You'll learn how to send SMS messages via MessageBird's REST API, configure and secure delivery status webhooks, and display real-time delivery updates in a modern Vite + React or Vue frontend application.
By the end of this guide, you will have:
Key Technologies:
System Architecture:
The system consists of three main components: a frontend dashboard (Vite + React/Vue), a Node.js backend API, and MessageBird's webhook service. When you send an SMS, MessageBird assigns it a unique message ID and begins delivery. As the delivery progresses through states (sent → buffered → delivered or failed), MessageBird sends HTTP POST webhooks to your server with status updates. Your webhook handler verifies the signature, updates the database, and the frontend polls or uses WebSockets to display real-time status.
Prerequisites:
Understanding MessageBird Delivery Status Webhooks
Before diving into implementation, it's essential to understand how MessageBird delivery status webhooks work and why they're critical for production SMS applications.
What Are SMS Delivery Status Webhooks?
When you send an SMS via MessageBird, the API returns a
201 Createdresponse with a message ID almost instantly. However, this doesn't mean the SMS has been delivered to the recipient's phone – it only confirms MessageBird accepted your request. The actual delivery process involves:Delivery status webhooks are HTTP POST callbacks that MessageBird sends to your server URL whenever a message's status changes. This enables you to:
MessageBird Webhook Payload Structure
MessageBird sends webhooks with a JSON payload containing delivery information. Here's a typical webhook payload for a delivered message:
{ "id": "b65a6c749d7e4758a811ea7c8c7e0000", "href": "https://rest.messagebird.com/messages/b65a6c749d7e4758a811ea7c8c7e0000", "direction": "mt", "type": "sms", "originator": "YourBrand", "body": "Your verification code is 123456", "reference": "customer-order-5678", "validity": null, "gateway": 10, "typeDetails": {}, "datacoding": "plain", "mclass": 1, "scheduledDatetime": null, "createdDatetime": "2025-10-10T14:30:00+00:00", "recipients": { "totalCount": 1, "totalSentCount": 1, "totalDeliveredCount": 1, "totalDeliveryFailedCount": 0, "items": [ { "recipient": 14155552671, "status": "delivered", "statusDatetime": "2025-10-10T14:30:15+00:00", "messagePartCount": 1 } ] } }Key Fields for Delivery Tracking:
id: MessageBird's unique message identifier (use this to correlate with your database)recipients.items[].status: The delivery status (sent,buffered,delivered,delivery_failed,expired)recipients.items[].statusDatetime: Timestamp when the status was updatedrecipients.items[].recipient: The phone number that received (or failed to receive) the messagereference: Your custom reference ID (optional but recommended for linking to orders, users, etc.)Webhook Security: Signature Verification
Critical: Always verify webhook signatures to prevent malicious actors from sending fake delivery status updates to your system. MessageBird signs each webhook request using HMAC-SHA256 with your signing key.
The signature is sent in the
MessageBird-SignatureHTTP header. To verify:MessageBird-Signatureheader valueExample verification code (we'll implement this in detail later):
const crypto = require('crypto'); function verifyWebhookSignature(signature, body, signingKey) { const computedSignature = crypto .createHmac('sha256', signingKey) .update(body, 'utf8') .digest('base64'); return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(computedSignature) ); }Setting Up Your Development Environment
We'll start by setting up the Node.js backend server that handles SMS sending and webhook callbacks.
1. Initialize Node.js Project
Create a new project directory and initialize npm:
mkdir messagebird-delivery-tracker cd messagebird-delivery-tracker npm init -y2. Install Backend Dependencies
Install the required packages for the Node.js backend:
Package overview:
express: Web framework for REST API and webhook endpointsmessagebird: Official MessageBird Node.js SDKdotenv: Environment variable managementbody-parser: Middleware to parse webhook JSON payloads (also need raw body for signature verification)For database integration (optional but recommended), install:
# PostgreSQL npm install pg # Or MongoDB npm install mongodb # Or Prisma (works with PostgreSQL, MySQL, SQLite) npm install prisma @prisma/client npx prisma init3. Configure Environment Variables
Create a
.envfile in your project root with your MessageBird credentials:Important: Never commit the
.envfile to version control. Add it to.gitignore:4. Obtain MessageBird Credentials
Implementing the Backend API
Create
server.jsas your main backend entry point:// server.js require('dotenv').config(); const express = require('express'); const bodyParser = require('body-parser'); const crypto = require('crypto'); const messagebird = require('messagebird')(process.env.MESSAGEBIRD_ACCESS_KEY); const app = express(); const PORT = process.env.PORT || 3000; // Middleware app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); // In-memory storage (replace with database in production) const messages = new Map(); // Helper: Verify MessageBird webhook signature function verifyWebhookSignature(req, res, next) { const signature = req.headers['messagebird-signature']; const signingKey = process.env.MESSAGEBIRD_SIGNING_KEY; if (!signature) { console.error('Missing MessageBird-Signature header'); return res.status(401).json({ error: 'Unauthorized: Missing signature' }); } // Get raw body (important: must be the exact bytes MessageBird signed) const rawBody = JSON.stringify(req.body); const computedSignature = crypto .createHmac('sha256', signingKey) .update(rawBody, 'utf8') .digest('base64'); try { const isValid = crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(computedSignature) ); if (!isValid) { console.error('Invalid webhook signature'); return res.status(401).json({ error: 'Unauthorized: Invalid signature' }); } next(); } catch (error) { console.error('Signature verification error:', error); return res.status(401).json({ error: 'Unauthorized: Signature verification failed' }); } } // API Endpoint: Send SMS app.post('/api/sms/send', async (req, res) => { const { recipient, message, reference } = req.body; if (!recipient || !message) { return res.status(400).json({ error: 'Missing required fields: recipient, message' }); } const params = { originator: process.env.MESSAGEBIRD_ORIGINATOR, recipients: [recipient], body: message, reference: reference || `msg_${Date.now()}`, reportUrl: process.env.WEBHOOK_URL }; messagebird.messages.create(params, (err, response) => { if (err) { console.error('MessageBird API Error:', err); return res.status(500).json({ error: 'Failed to send SMS', details: err }); } // Store message in database/memory messages.set(response.id, { id: response.id, recipient: response.recipients.items[0].recipient, body: response.body, status: response.recipients.items[0].status, createdAt: response.createdDatetime, reference: response.reference }); res.status(201).json({ success: true, messageId: response.id, status: response.recipients.items[0].status, reference: response.reference }); }); }); // Webhook Endpoint: Receive delivery status updates app.post('/webhooks/messagebird/delivery', verifyWebhookSignature, (req, res) => { const webhookData = req.body; console.log('Received webhook:', JSON.stringify(webhookData, null, 2)); const messageId = webhookData.id; const recipient = webhookData.recipients.items[0]; // Update message status in database/memory if (messages.has(messageId)) { const message = messages.get(messageId); message.status = recipient.status; message.statusDatetime = recipient.statusDatetime; messages.set(messageId, message); console.log(`Updated message ${messageId} status to: ${recipient.status}`); } else { console.warn(`Received webhook for unknown message ID: ${messageId}`); } // Always return 200 OK to acknowledge receipt res.status(200).json({ received: true }); }); // API Endpoint: Get message status app.get('/api/sms/:messageId', (req, res) => { const messageId = req.params.messageId; if (!messages.has(messageId)) { return res.status(404).json({ error: 'Message not found' }); } res.json(messages.get(messageId)); }); // API Endpoint: List all messages app.get('/api/sms', (req, res) => { res.json(Array.from(messages.values())); }); // Health check app.get('/health', (req, res) => { res.json({ status: 'ok', timestamp: new Date().toISOString() }); }); // Start server app.listen(PORT, () => { console.log(`Server running on http://localhost:${PORT}`); console.log(`Webhook endpoint: http://localhost:${PORT}/webhooks/messagebird/delivery`); });Key Implementation Details:
verifyWebhookSignaturefunction runs before the webhook handler, rejecting invalid requestsmessagebird.messages.create(), we includereportUrlpointing to our webhook endpointMap. In production, replace with PostgreSQL, MongoDB, or RedisDatabase Schema for Message Tracking
For production applications, implement a proper database schema to persist message delivery status. Here's an example using PostgreSQL with Prisma:
Prisma Schema (schema.prisma)
Status Values:
pending,sent,buffered,delivered,delivery_failed,expiredDatabase Integration Code
Replace the in-memory
Mapwith Prisma:const { PrismaClient } = require('@prisma/client'); const prisma = new PrismaClient(); // In send endpoint: const dbMessage = await prisma.message.create({ data: { messageBirdId: response.id, recipient: response.recipients.items[0].recipient, body: response.body, originator: response.originator, status: response.recipients.items[0].status, reference: response.reference } }); // In webhook handler: await prisma.message.update({ where: { messageBirdId: messageId }, data: { status: recipient.status, statusDatetime: new Date(recipient.statusDatetime) } });Building the Vite Frontend
Now let's create the frontend dashboard to display real-time delivery status using Vite with React or Vue.
Initialize Vite Project
# For React npm create vite@latest frontend -- --template react # Or for Vue npm create vite@latest frontend -- --template vue cd frontend npm install npm install axiosReact Component: Message Status Dashboard
Create
src/components/MessageDashboard.jsx:import { useState, useEffect } from 'react'; import axios from 'axios'; const API_BASE_URL = 'http://localhost:3000'; function MessageDashboard() { const [messages, setMessages] = useState([]); const [recipient, setRecipient] = useState(''); const [messageText, setMessageText] = useState(''); const [loading, setLoading] = useState(false); // Fetch messages periodically useEffect(() => { fetchMessages(); const interval = setInterval(fetchMessages, 5000); return () => clearInterval(interval); }, []); const fetchMessages = async () => { try { const response = await axios.get(`${API_BASE_URL}/api/sms`); setMessages(response.data); } catch (error) { console.error('Failed to fetch messages:', error); } }; const sendSMS = async (e) => { e.preventDefault(); setLoading(true); try { await axios.post(`${API_BASE_URL}/api/sms/send`, { recipient, message: messageText, reference: `web_${Date.now()}` }); setRecipient(''); setMessageText(''); fetchMessages(); } catch (error) { console.error('Failed to send SMS:', error); alert('Failed to send SMS: ' + error.message); } finally { setLoading(false); } }; const getStatusColor = (status) => { const colors = { pending: 'gray', sent: 'blue', buffered: 'yellow', delivered: 'green', delivery_failed: 'red', expired: 'orange' }; return colors[status] || 'gray'; }; return ( <div style={{ padding: '20px', maxWidth: '1200px', margin: '0 auto' }}> <h1>MessageBird SMS Delivery Tracker</h1> {/* Send SMS Form */} <div style={{ marginBottom: '30px', padding: '20px', border: '1px solid #ddd', borderRadius: '8px' }}> <h2>Send New SMS</h2> <form onSubmit={sendSMS}> <div style={{ marginBottom: '10px' }}> <label> Recipient (E.164 format): <input type="tel" value={recipient} onChange={(e) => setRecipient(e.target.value)} placeholder="+14155552671" required style={{ marginLeft: '10px', padding: '5px', width: '200px' }} /> </label> </div> <div style={{ marginBottom: '10px' }}> <label> Message: <textarea value={messageText} onChange={(e) => setMessageText(e.target.value)} placeholder="Your message here..." required rows={3} style={{ marginLeft: '10px', padding: '5px', width: '300px' }} /> </label> </div> <button type="submit" disabled={loading} style={{ padding: '10px 20px' }}> {loading ? 'Sending...' : 'Send SMS'} </button> </form> </div> {/* Messages Table */} <div> <h2>Message Delivery Status ({messages.length} messages)</h2> <table style={{ width: '100%', borderCollapse: 'collapse' }}> <thead> <tr style={{ backgroundColor: '#f0f0f0' }}> <th style={{ padding: '10px', border: '1px solid #ddd' }}>Recipient</th> <th style={{ padding: '10px', border: '1px solid #ddd' }}>Message</th> <th style={{ padding: '10px', border: '1px solid #ddd' }}>Status</th> <th style={{ padding: '10px', border: '1px solid #ddd' }}>Created</th> <th style={{ padding: '10px', border: '1px solid #ddd' }}>Reference</th> </tr> </thead> <tbody> {messages.map((msg) => ( <tr key={msg.id}> <td style={{ padding: '10px', border: '1px solid #ddd' }}>{msg.recipient}</td> <td style={{ padding: '10px', border: '1px solid #ddd' }}>{msg.body.substring(0, 50)}...</td> <td style={{ padding: '10px', border: '1px solid #ddd' }}> <span style={{ padding: '5px 10px', borderRadius: '4px', backgroundColor: getStatusColor(msg.status), color: 'white', fontWeight: 'bold' }}> {msg.status.toUpperCase()} </span> </td> <td style={{ padding: '10px', border: '1px solid #ddd' }}> {new Date(msg.createdAt).toLocaleString()} </td> <td style={{ padding: '10px', border: '1px solid #ddd' }}>{msg.reference || 'N/A'}</td> </tr> ))} </tbody> </table> </div> </div> ); } export default MessageDashboard;Vue Component Alternative
For Vue users, create
src/components/MessageDashboard.vue:Testing Webhooks Locally with ngrok
MessageBird needs a publicly accessible HTTPS URL to send webhooks. For local development, use ngrok:
1. Install and Run ngrok
# Install ngrok (or download from https://ngrok.com) npm install -g ngrok # Start your backend server node server.js # In a new terminal, expose port 3000 ngrok http 3000ngrok will provide a public URL like
https://abc123.ngrok.io.2. Update Environment Variables
Update your
.envfile with the ngrok URL:3. Configure MessageBird Dashboard (Optional)
MessageBird also allows configuring default webhook URLs in the dashboard:
Note: The
reportUrlparameter in the send request overrides the dashboard default.4. Test the Complete Flow
node server.jscd frontend && npm run devhttp://localhost:5173)Check your backend logs for webhook payloads:
Security Best Practices
1. Webhook Signature Verification
Always verify the
MessageBird-Signatureheader. Never process webhooks without verification:// Bad: No verification app.post('/webhooks/messagebird/delivery', (req, res) => { // ❌ Anyone can send fake webhooks updateMessageStatus(req.body); }); // Good: Signature verification app.post('/webhooks/messagebird/delivery', verifyWebhookSignature, (req, res) => { // ✅ Only authentic MessageBird webhooks processed updateMessageStatus(req.body); });2. Rate Limiting
Protect your endpoints from abuse:
const rateLimit = require('express-rate-limit'); const webhookLimiter = rateLimit({ windowMs: 1 * 60 * 1000, // 1 minute max: 100, // Limit each IP to 100 requests per minute message: 'Too many webhook requests' }); app.post('/webhooks/messagebird/delivery', webhookLimiter, verifyWebhookSignature, handler);3. HTTPS in Production
Always use HTTPS in production to prevent man-in-the-middle attacks:
4. Environment Variable Security
.envfiles to version controlError Handling and Retry Logic
Handling Webhook Delivery Failures
MessageBird will retry failed webhook deliveries up to 10 times with exponential backoff. Ensure your webhook handler:
app.post('/webhooks/messagebird/delivery', verifyWebhookSignature, async (req, res) => { // Immediately acknowledge receipt res.status(200).json({ received: true }); // Process asynchronously try { await processWebhook(req.body); } catch (error) { console.error('Webhook processing error:', error); // Log to error tracking service (Sentry, Rollbar, etc.) } });Handling SMS Send Failures
Implement retry logic for transient failures:
async function sendSMSWithRetry(params, maxRetries = 3) { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await messagebird.messages.create(params); } catch (error) { if (attempt === maxRetries || !isRetryableError(error)) { throw error; } await delay(1000 * Math.pow(2, attempt)); // Exponential backoff } } } function isRetryableError(error) { // Retry on rate limits and temporary failures return error.statusCode === 429 || error.statusCode >= 500; }Deployment
Backend Deployment (Node.js)
Deploy your backend to platforms that support webhooks:
Heroku:
heroku create your-app-name heroku config:set MESSAGEBIRD_ACCESS_KEY=your_key heroku config:set MESSAGEBIRD_SIGNING_KEY=your_signing_key git push heroku mainRailway:
AWS Lambda + API Gateway:
Use the Serverless Framework or AWS SAM to deploy the Express app as Lambda functions.
Frontend Deployment (Vite)
Vercel:
cd frontend vercel deploy --prodNetlify:
cd frontend npm run build netlify deploy --prod --dir=distUpdate your frontend API calls to use the production backend URL.
Monitoring and Analytics
Track Delivery Metrics
Monitor key SMS delivery metrics:
// Calculate delivery rates const totalMessages = await prisma.message.count(); const deliveredMessages = await prisma.message.count({ where: { status: 'delivered' } }); const failedMessages = await prisma.message.count({ where: { status: 'delivery_failed' } }); const deliveryRate = (deliveredMessages / totalMessages) * 100; const failureRate = (failedMessages / totalMessages) * 100; console.log(`Delivery rate: ${deliveryRate.toFixed(2)}%`); console.log(`Failure rate: ${failureRate.toFixed(2)}%`);Integration with Analytics Tools
Send delivery metrics to monitoring platforms:
// Example: Send metrics to Datadog const StatsD = require('node-dogstatsd').StatsD; const dogstatsd = new StatsD(); function recordDeliveryStatus(status) { dogstatsd.increment('sms.delivery', 1, [`status:${status}`]); } // In webhook handler recordDeliveryStatus(recipient.status);Troubleshooting Common Issues
Webhooks Not Being Received
Possible causes:
reportUrlparameter matches your actual endpointSolutions:
# Check if webhook endpoint is accessible curl -X POST https://your-domain.com/webhooks/messagebird/delivery \ -H "Content-Type: application/json" \ -d '{"test": true}' # Test locally with ngrok ngrok http 3000 # Update WEBHOOK_URL in .env and restart serverSignature Verification Failures
Possible causes:
Solution: Enable debug logging:
function verifyWebhookSignature(req, res, next) { const signature = req.headers['messagebird-signature']; const rawBody = JSON.stringify(req.body); console.log('Received signature:', signature); console.log('Raw body:', rawBody); console.log('Signing key (first 10 chars):', process.env.MESSAGEBIRD_SIGNING_KEY.substring(0, 10)); // ... verification logic }Messages Stuck in "Sent" Status
Possible causes:
Solution: Query MessageBird API directly:
messagebird.messages.read(messageId, (err, response) => { console.log('Current status:', response.recipients.items[0].status); });Related Resources
Frequently Asked Questions
How do I verify MessageBird webhook signatures?
Extract the
MessageBird-Signatureheader, compute HMAC-SHA256 of the raw request body using your signing key, and compare with the header value usingcrypto.timingSafeEqual()to prevent timing attacks.What MessageBird delivery statuses should I handle?
Handle these key statuses:
sent(handed to carrier),buffered(queued at carrier),delivered(confirmed receipt),delivery_failed(permanent failure), andexpired(validity period exceeded).Can I use Vite with other frameworks besides React and Vue?
Yes, Vite supports Svelte, Preact, Lit, and vanilla JavaScript. Use
npm create vite@latestand select your preferred framework template.How long do MessageBird webhooks take to arrive?
Webhooks typically arrive within 1-30 seconds after status changes. Delivery confirmations depend on carrier speed (usually 5-60 seconds for most carriers).
Do I need a database for production deployments?
Yes, use PostgreSQL, MySQL, or MongoDB to persist message status across server restarts. In-memory storage loses data when the server stops.
How can I test webhooks without deploying to production?
Use ngrok to expose your local development server with a public HTTPS URL. MessageBird will send webhooks to your localhost through the ngrok tunnel.
What happens if my webhook endpoint is down?
MessageBird retries failed webhooks up to 10 times with exponential backoff (up to 24 hours). Ensure your endpoint returns 200 OK quickly to avoid retries.
Can I configure different webhook URLs for different messages?
Yes, use the
reportUrlparameter when sending each message. This overrides the default webhook URL configured in your MessageBird dashboard.How do I handle duplicate webhook deliveries?
Use the message ID as a unique key in your database. When updating status, use
UPDATE WHERE messageBirdId = ?which safely handles duplicate webhooks.What's the difference between MessageBird's signing key and access key?
The access key authenticates API requests you send to MessageBird. The signing key validates webhook requests MessageBird sends to you. Never share either key.