Skooh.io Logo
Back to blog
IntegrationDeveloper TutorialOctober 22, 20257 min

Twilio Status Callbacks to an Internal Service Using Skooh

Forward Twilio SMS and voice status callbacks to private handlers. Handle retries and duplicates gracefully.

Overview

Twilio sends status callbacks for SMS delivery, voice call events, and more. These callbacks let you track message delivery, handle call recordings, and respond to errors.

With Skooh, your internal callback handlers receive these events without being exposed to the internet.

Setting Up

1. Create Skooh Endpoint

Create an endpoint in Skooh and copy your webhook URL.

2. Run the Skooh Proxy

Using the Desktop App:

  1. Download the Skooh Desktop App
  2. Enter your API key and hook token
  3. Set forwarding URL (e.g., `http://localhost:3000/callbacks/twilio\`)
  4. Click Connect

Using Docker:

docker run -d \\
  --name skooh-twilio \\
  -e SKOOH_TOKEN=your_token \\
  -e SKOOH_API_KEY=your_api_key \\
  -e FORWARD_URL=http://messaging-service:3000/callbacks/twilio \\
  --restart unless-stopped \\
  skooh/proxy

3. Configure Twilio

Note: See Twilio's webhook documentation for current configuration options.

When sending SMS, include your Skooh URL as the status callback:

const twilio = require('twilio');
const client = twilio(accountSid, authToken);

await client.messages.create({
  body: 'Hello from Twilio!',
  from: '+1234567890',
  to: '+0987654321',
  statusCallback: 'https://api.skooh.io/hook/your_token'
});

Or configure it in your TwiML apps and phone numbers.

Handling Status Callbacks

Twilio sends form-encoded data (not JSON):

const express = require('express');
const app = express();

// Parse form data
app.use(express.urlencoded({ extended: false }));

app.post('/callbacks/twilio', (req, res) => {
  const {
    MessageSid,
    MessageStatus,
    To,
    From,
    ErrorCode,
    ErrorMessage
  } = req.body;
  
  console.log(\`Message \${MessageSid}: \${MessageStatus}\`);
  
  switch (MessageStatus) {
    case 'queued':
      // Message accepted by Twilio
      break;
    case 'sent':
      // Message sent to carrier
      break;
    case 'delivered':
      // Message delivered to device
      await markMessageDelivered(MessageSid);
      break;
    case 'failed':
    case 'undelivered':
      await handleDeliveryFailure(MessageSid, ErrorCode, ErrorMessage);
      break;
  }
  
  // Twilio expects empty 200 response
  res.status(200).send();
});

Validating Twilio Requests

Twilio signs requests. Always verify in production:

const twilio = require('twilio');

function validateTwilioRequest(req) {
  const authToken = process.env.TWILIO_AUTH_TOKEN;
  const twilioSignature = req.headers['x-twilio-signature'];
  const url = 'https://api.skooh.io/hook/your_token'; // Your Skooh URL
  
  return twilio.validateRequest(
    authToken,
    twilioSignature,
    url,
    req.body
  );
}

app.post('/callbacks/twilio', (req, res) => {
  if (!validateTwilioRequest(req)) {
    return res.status(403).send('Invalid signature');
  }
  // Process callback...
});

Handling Retries and Duplicates

Twilio retries failed callbacks. Make your handlers idempotent:

const processedCallbacks = new Map(); // Use Redis in production

app.post('/callbacks/twilio', async (req, res) => {
  const { MessageSid, MessageStatus } = req.body;
  const callbackKey = \`\${MessageSid}:\${MessageStatus}\`;
  
  // Check for duplicate
  if (processedCallbacks.has(callbackKey)) {
    console.log(\`Duplicate callback for \${callbackKey}, skipping\`);
    return res.status(200).send();
  }
  
  // Process the callback
  await processStatusUpdate(req.body);
  processedCallbacks.set(callbackKey, Date.now());
  
  res.status(200).send();
});

Voice Call Status Callbacks

Voice calls also trigger status callbacks:

app.post('/callbacks/twilio', (req, res) => {
  const { CallSid, CallStatus, Duration, RecordingUrl } = req.body;
  
  switch (CallStatus) {
    case 'ringing':
      await logCallStarted(CallSid);
      break;
    case 'in-progress':
      await updateCallStatus(CallSid, 'connected');
      break;
    case 'completed':
      await handleCallComplete(CallSid, Duration, RecordingUrl);
      break;
    case 'failed':
    case 'no-answer':
    case 'busy':
      await handleCallFailure(CallSid, CallStatus);
      break;
  }
  
  res.status(200).send();
});

Using Replay for Testing

  1. Send a test SMS
  2. Wait for status callbacks in Skooh dashboard
  3. Click Replay to test your handler
  4. Test different status scenarios without sending more messages

Conclusion

Skooh bridges Twilio's callbacks to your internal services. Combined with proper validation and idempotency, you get reliable SMS/voice tracking behind your firewall.

Set up Twilio callbacks →

Written by Skooh Team

Share:

Ready to try Skooh?

Start receiving webhooks in your local environment in under 5 minutes.

Skooh | Twilio Status Callbacks to an Internal Service Using Skooh