Twilio Integration
Learn how to integrate Twilio webhooks with HooksDash for testing SMS and call events locally.
Setup
1. Create a HooksDash Endpoint
# Via CLI hooksdash endpoints create --name "Twilio Development" --slug twilio-dev # Your webhook URL: https://hooksdash-api.ibadsiddiqui.dev/api/h/twilio-dev
2. Configure in Twilio
For Phone Numbers:
- Go to Twilio Console
- Navigate to Phone Numbers → Manage → Active numbers
- Click on your phone number
- Scroll to Messaging section
- Configure webhook:
https://hooksdash-api.ibadsiddiqui.dev/api/h/twilio-dev - Select HTTP POST
- Click Save
For Call Status:
- In the same phone number settings
- Scroll to Voice & Fax section
- Configure webhook for incoming calls or status callbacks
- Click Save
Implementation
SMS Handler
const express = require('express'); const twilio = require('twilio'); const app = express(); app.use(express.urlencoded({ extended: false })); app.post('/webhook', (req, res) => { const twiml = new twilio.twiml.MessagingResponse(); const from = req.body.From; const body = req.body.Body; console.log(`SMS from ${from}: ${body}`); // Auto-reply logic if (body.toLowerCase().includes('help')) { twiml.message('Available commands: STATUS, HELP, INFO'); } else if (body.toLowerCase().includes('status')) { twiml.message('System status: All services operational'); } else { twiml.message('Thank you for your message!'); } res.type('text/xml'); res.send(twiml.toString()); }); app.listen(3000);
Voice Call Handler
const express = require('express'); const twilio = require('twilio'); const app = express(); app.use(express.urlencoded({ extended: false })); app.post('/voice', (req, res) => { const twiml = new twilio.twiml.VoiceResponse(); const from = req.body.From; console.log(`Call from ${from}`); // Greet the caller twiml.say( { voice: 'alice' }, 'Thank you for calling. Please leave a message after the beep.' ); // Record the message twiml.record({ maxLength: 30, action: '/recording', }); res.type('text/xml'); res.send(twiml.toString()); }); app.post('/recording', (req, res) => { const recordingUrl = req.body.RecordingUrl; console.log('Recording URL:', recordingUrl); const twiml = new twilio.twiml.VoiceResponse(); twiml.say('Thank you. Goodbye!'); res.type('text/xml'); res.send(twiml.toString()); }); app.listen(3000);
TypeScript Implementation
import express from 'express'; import twilio from 'twilio'; const app = express(); app.use(express.urlencoded({ extended: false })); interface TwilioSMSRequest extends express.Request { body: { MessageSid: string; From: string; To: string; Body: string; NumMedia?: string; }; } app.post('/webhook', (req: TwilioSMSRequest, res) => { const twiml = new twilio.twiml.MessagingResponse(); const { From, Body } = req.body; console.log(`SMS from ${From}: ${Body}`); // Process message const response = processMessage(Body); twiml.message(response); res.type('text/xml'); res.send(twiml.toString()); }); function processMessage(message: string): string { const lowerMessage = message.toLowerCase(); if (lowerMessage.includes('help')) { return 'Available commands: STATUS, HELP, INFO'; } if (lowerMessage.includes('status')) { return 'System status: All services operational'; } return 'Thank you for your message!'; } app.listen(3000);
Common Webhook Parameters
SMS Webhooks
| Parameter | Description |
|---|---|
MessageSid | Unique identifier for the message |
From | Phone number that sent the message |
To | Your Twilio phone number |
Body | Text content of the message |
NumMedia | Number of media files attached |
MediaUrl0 | URL of the first media file |
Voice Webhooks
| Parameter | Description |
|---|---|
CallSid | Unique identifier for the call |
From | Caller's phone number |
To | Your Twilio phone number |
CallStatus | Status of the call (ringing, in-progress, completed) |
Direction | inbound or outbound |
Testing Locally
1. Start Your Server
node server.js
2. Start HooksDash Tunnel
hooksdash forward --port 3000 --endpoint twilio-dev
3. Test SMS
Send an SMS to your Twilio phone number. The webhook will be forwarded to your local server!
4. Test Voice
Call your Twilio phone number to test voice webhooks.
Example Use Cases
SMS Bot
const commands = { subscribe: (from) => { // Add to subscription list subscriptions.add(from); return 'You are now subscribed to updates!'; }, unsubscribe: (from) => { // Remove from subscription list subscriptions.delete(from); return 'You have been unsubscribed.'; }, status: () => { return 'System status: All services operational'; }, help: () => { return 'Commands: SUBSCRIBE, UNSUBSCRIBE, STATUS, HELP'; }, }; app.post('/webhook', (req, res) => { const twiml = new twilio.twiml.MessagingResponse(); const { From, Body } = req.body; const command = Body.toLowerCase().trim(); const handler = commands[command]; const response = handler ? handler(From) : 'Unknown command. Text HELP for options.'; twiml.message(response); res.type('text/xml'); res.send(twiml.toString()); });
Two-Factor Authentication
const twilio = require('twilio'); const client = twilio( process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN ); // Send verification code async function sendVerificationCode(phoneNumber) { const code = Math.floor(100000 + Math.random() * 900000); await client.messages.create({ body: `Your verification code is: ${code}`, from: process.env.TWILIO_PHONE_NUMBER, to: phoneNumber, }); return code; } // Verify code via SMS reply app.post('/webhook', (req, res) => { const twiml = new twilio.twiml.MessagingResponse(); const { From, Body } = req.body; const isValid = verifyCode(From, Body); if (isValid) { twiml.message('Verification successful!'); // Grant access to user } else { twiml.message('Invalid code. Please try again.'); } res.type('text/xml'); res.send(twiml.toString()); });
Call Status Tracking
app.post('/call-status', (req, res) => { const { CallSid, CallStatus, CallDuration } = req.body; console.log(`Call ${CallSid} status: ${CallStatus}`); if (CallStatus === 'completed') { console.log(`Call duration: ${CallDuration} seconds`); // Log call details, update analytics, etc. } res.status(200).send('OK'); });
Best Practices
1. Validate Requests
Verify that requests come from Twilio:
const twilio = require('twilio'); app.post('/webhook', (req, res) => { const twilioSignature = req.headers['x-twilio-signature']; const url = `https://yourapp.com/webhook`; const isValid = twilio.validateRequest( process.env.TWILIO_AUTH_TOKEN, twilioSignature, url, req.body ); if (!isValid) { return res.status(403).send('Invalid signature'); } // Process webhook... });
2. Respond with TwiML
Always respond with valid TwiML for SMS and voice webhooks:
app.post('/webhook', (req, res) => { const twiml = new twilio.twiml.MessagingResponse(); twiml.message('Your response here'); res.type('text/xml'); res.send(twiml.toString()); });
3. Handle Media Messages
Check for media attachments in SMS:
app.post('/webhook', async (req, res) => { const { NumMedia } = req.body; if (parseInt(NumMedia) > 0) { const mediaUrl = req.body.MediaUrl0; console.log('Received media:', mediaUrl); // Download and process media } const twiml = new twilio.twiml.MessagingResponse(); twiml.message('Media received!'); res.type('text/xml'); res.send(twiml.toString()); });
Troubleshooting
Webhook Not Receiving Messages
Issue: SMS sent to Twilio number but webhook not triggered
Solution:
- Verify the webhook URL is configured in Twilio console
- Check that HTTP POST is selected
- Ensure the phone number has SMS capabilities
- Check Twilio's debugger logs for delivery status
Invalid TwiML Response
Issue: 11200 HTTP retrieval failure error
Solution:
- Ensure you're responding with valid TwiML XML
- Set content type to
text/xml - Return response within 10 seconds
Local Forwarding Issues
Issue: Webhook appears in HooksDash but not forwarded to localhost
Solution:
- Ensure the CLI tunnel is running
- Verify your local server is running and accessible
- Check that you're using the correct port number