Twilio Integration
Receive and route Twilio webhooks for SMS messages, voice calls, and other communication events.
Setup
1. Create a Source in Hookbase
bash
curl -X POST https://api.hookbase.app/api/sources \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Twilio",
"slug": "twilio",
"provider": "twilio",
"verificationConfig": {
"type": "twilio",
"secret": "your-twilio-auth-token"
}
}'Save your webhook URL:
https://api.hookbase.app/ingest/{orgSlug}/twilio2. Configure Twilio Webhooks
For SMS/MMS
- Go to Twilio Console → Phone Numbers
- Select your phone number
- Under Messaging, set:
- A message comes in: Webhook, your Hookbase URL
- HTTP Method: POST
For Voice
- Go to Phone Numbers → select your number
- Under Voice & Fax, set:
- A call comes in: Webhook, your Hookbase URL
- HTTP Method: POST
For Status Callbacks
Set the status callback URL on your API requests:
bash
curl -X POST "https://api.twilio.com/2010-04-01/Accounts/{AccountSid}/Messages.json" \
-u "{AccountSid}:{AuthToken}" \
-d "To=+15558675309" \
-d "From=+15551234567" \
-d "Body=Hello" \
-d "StatusCallback=https://api.hookbase.app/ingest/{orgSlug}/twilio"3. Create Destinations and Routes
bash
# Create a destination
curl -X POST https://api.hookbase.app/api/destinations \
-H "Authorization: Bearer whr_your_api_key" \
-d '{"name": "SMS Handler", "url": "https://myapp.com/webhooks/twilio"}'
# Create a route
curl -X POST https://api.hookbase.app/api/routes \
-H "Authorization: Bearer whr_your_api_key" \
-d '{"name": "Twilio to App", "sourceId": "src_...", "destinationIds": ["dst_..."]}'Signature Verification
Twilio signs webhooks using your Auth Token. The signature is sent in the X-Twilio-Signature header.
Hookbase automatically verifies this when you configure:
json
{
"verificationConfig": {
"type": "twilio",
"secret": "your-twilio-auth-token"
}
}TIP
The secret is your Twilio Auth Token, found on the Twilio Console dashboard.
Common Events
Incoming SMS
json
{
"MessageSid": "SM1234567890abcdef",
"AccountSid": "AC1234567890abcdef",
"From": "+15551234567",
"To": "+15558675309",
"Body": "Hello from the customer",
"NumMedia": "0",
"FromCity": "NEW YORK",
"FromState": "NY",
"FromCountry": "US"
}SMS Status Callback
json
{
"MessageSid": "SM1234567890abcdef",
"MessageStatus": "delivered",
"To": "+15558675309",
"From": "+15551234567",
"ApiVersion": "2010-04-01",
"AccountSid": "AC1234567890abcdef"
}Message Statuses
| Status | Description |
|---|---|
queued | Message queued for sending |
sending | Message is being sent |
sent | Message sent to carrier |
delivered | Message delivered to recipient |
undelivered | Message could not be delivered |
failed | Message failed to send |
Voice Call
json
{
"CallSid": "CA1234567890abcdef",
"AccountSid": "AC1234567890abcdef",
"From": "+15551234567",
"To": "+15558675309",
"CallStatus": "ringing",
"Direction": "inbound",
"CallerCity": "NEW YORK",
"CallerState": "NY",
"CallerCountry": "US"
}Call Statuses
| Status | Description |
|---|---|
queued | Call queued |
ringing | Phone is ringing |
in-progress | Call connected |
completed | Call ended normally |
busy | Busy signal |
no-answer | No answer |
failed | Call failed |
Transform Examples
Slack Notification for SMS
javascript
function transform(payload) {
return {
blocks: [
{
type: "header",
text: {
type: "plain_text",
text: "Incoming SMS"
}
},
{
type: "section",
fields: [
{ type: "mrkdwn", text: `*From:*\n${payload.From}` },
{ type: "mrkdwn", text: `*To:*\n${payload.To}` }
]
},
{
type: "section",
text: {
type: "mrkdwn",
text: `*Message:*\n${payload.Body}`
}
}
]
};
}Normalize for Internal API
javascript
function transform(payload) {
return {
type: payload.CallSid ? 'call' : 'sms',
sid: payload.MessageSid || payload.CallSid,
from: payload.From,
to: payload.To,
body: payload.Body || null,
status: payload.MessageStatus || payload.CallStatus,
location: {
city: payload.FromCity || payload.CallerCity,
state: payload.FromState || payload.CallerState,
country: payload.FromCountry || payload.CallerCountry
},
timestamp: new Date().toISOString()
};
}Filter Examples
Only Inbound SMS
json
{
"name": "Inbound SMS Only",
"conditions": [
{
"field": "payload.MessageSid",
"operator": "exists"
},
{
"field": "payload.Body",
"operator": "exists"
}
],
"logic": "AND"
}Failed Deliveries Only
json
{
"name": "Failed Messages",
"conditions": [
{
"field": "payload.MessageStatus",
"operator": "in",
"value": "failed,undelivered"
}
]
}Twilio Webhook Parameters
SMS Parameters
| Parameter | Description |
|---|---|
MessageSid | Unique message ID |
AccountSid | Twilio account ID |
From | Sender phone number |
To | Recipient phone number |
Body | Message text |
NumMedia | Number of media attachments |
MediaUrl{N} | URL for media attachment N |
FromCity | Sender's city |
FromState | Sender's state |
FromCountry | Sender's country |
Voice Parameters
| Parameter | Description |
|---|---|
CallSid | Unique call ID |
AccountSid | Twilio account ID |
From | Caller phone number |
To | Called phone number |
CallStatus | Current call status |
Direction | inbound or outbound-api |
CallerCity | Caller's city |
CallerState | Caller's state |
CallerCountry | Caller's country |
Troubleshooting
Webhook Not Triggering
- Verify the URL is set correctly in the Twilio Console
- Check Twilio's Debugger for errors
- Ensure the phone number is configured for the correct service (SMS/Voice)
Signature Verification Failed
- Use your Auth Token (not API Key SID) as the secret
- If using API Keys, use the account Auth Token, not the API Key Secret
- Ensure the webhook URL in Twilio matches exactly (including https://)
Duplicate Events
Enable deduplication using the MessageSid:
bash
curl -X PATCH .../sources/src_twilio \
-d '{"dedupEnabled": true, "dedupStrategy": "auto"}'Best Practices
- Verify signatures: Always enable signature verification
- Handle both formats: Twilio sends both form-encoded and JSON — Hookbase normalizes both to JSON
- Respond quickly: Twilio expects a response within 15 seconds for voice webhooks
- Use status callbacks: Track message delivery status for reliability
- Secure your endpoint: Use
rejectInvalidSignatures: truefor production