DEV Community

Cover image for Integrate Twilio with CRM using Low-Code Tools like Zapier and Make: My Journey
CallStack Tech
CallStack Tech

Posted on • Originally published at callstack.tech

Integrate Twilio with CRM using Low-Code Tools like Zapier and Make: My Journey

Integrate Twilio with CRM using Low-Code Tools like Zapier and Make: My Journey

TL;DR

Most CRM-to-SMS integrations fail because they're built backwards—treating Zapier as the source of truth instead of a routing layer. Here's what actually works: Twilio Programmable Messaging API handles SMS delivery, Zapier triggers on CRM events (new lead, deal closed), Make.com orchestrates complex workflows (conditional routing, retry logic), and Railway hosts your webhook handlers. Result: SMS notifications fire in <2s, AI phone agents auto-dial follow-ups, zero missed contacts.

Prerequisites

API Keys & Accounts

You'll need active accounts with Twilio, Zapier, and Make. Generate a Twilio API key and auth token from the Console (Settings > API Keys). Create a Zapier account and connect it to your CRM (Salesforce, HubSpot, Pipedrive—whatever you use). Set up a Make account and enable the Twilio connector module.

System Requirements

Node.js 16+ if you're deploying custom webhook handlers on Railway. A REST client (Postman, curl, or VS Code REST Client) for testing webhook payloads. Basic familiarity with JSON payloads and HTTP POST requests.

CRM Setup

Your CRM must expose API access (most modern platforms do). Ensure you have admin credentials and know your CRM's webhook event types (contact created, deal updated, etc.). Twilio Programmable Messaging API requires an active phone number provisioned in your account.

Network Access

Railway deployment needs a public URL for webhooks. Use ngrok or Railway's built-in domain for local testing. Firewall rules must allow outbound HTTPS to Twilio, Zapier, and Make endpoints.

Twilio: Get Twilio Voice API → Get Twilio

Step-by-Step Tutorial

Configuration & Setup

Provision your Twilio account and extract your Account SID and Auth Token from the console. Store these in environment variables—hardcoding credentials will bite you during the first security audit.

In Zapier, create a Zap with your CRM as the trigger. I used HubSpot's "New Deal" event. Configure field mappings to extract contact.phone, contact.name, and deal.amount. Test the trigger to verify Zapier receives sample data with all required fields populated.

For Make.com, the setup uses a visual canvas. Add your CRM module, authenticate via OAuth, and configure the webhook listener URL. Make's data inspector shows the entire payload structure, which cuts debugging time by 60% compared to Zapier's text-based logs.

Production failure I hit: Twilio's test credentials work for outbound SMS but silently fail on inbound webhooks. The API returns 200 but never delivers messages. Use live credentials from day one, even in staging.

Architecture & Flow

graph LR
    A[CRM Event] --> B[Zapier/Make]
    B --> C[Twilio API]
    C --> D[SMS Delivered]
    D --> E[Status Webhook]
    E --> F[CRM Update]
Enter fullscreen mode Exit fullscreen mode

When a deal closes, Zapier catches the webhook, extracts the contact's phone and deal metadata, then calls Twilio's Programmable Messaging API. Twilio sends the SMS and fires a MessageStatus callback to your webhook endpoint. That callback updates the CRM with delivery confirmation.

Why this breaks in production: If your webhook endpoint takes >5s to respond, Twilio retries 3 times then abandons the callback. Your CRM never receives delivery status. Solution: return HTTP 200 immediately, then process the update async using a job queue.

Step-by-Step Implementation

Step 1: Configure Twilio API Call in Zapier

Instead of using Zapier's built-in Twilio action (which hides error details), use the "Webhooks by Zapier" action to call Twilio's REST API directly:

// Zapier Webhook POST Configuration
// URL: https://api.twilio.com/2010-04-01/Accounts/{{ACCOUNT_SID}}/Messages.json
// Method: POST
// Headers:
//   Authorization: Basic {{base64(ACCOUNT_SID:AUTH_TOKEN)}}
//   Content-Type: application/x-www-form-urlencoded

// Body (form-encoded):
const messagePayload = {
  To: '+1{{contact.phone}}',  // E.164 format required
  From: '+15551234567',        // Your Twilio number
  Body: `Hi ${contact.name}, your deal for $${deal.amount} is confirmed. Reply STOP to opt out.`,
  StatusCallback: 'https://your-domain.com/webhook/twilio/status',  // YOUR server endpoint
  StatusCallbackMethod: 'POST'
};

// Zapier will URL-encode this automatically
// Expected response: { "sid": "SM...", "status": "queued" }
Enter fullscreen mode Exit fullscreen mode

Step 2: Build Webhook Handler for Delivery Status

Twilio sends delivery receipts to your server. Here's a production-grade Express handler:

// YOUR server receives Twilio webhooks here
const express = require('express');
const crypto = require('crypto');
const app = express();

app.use(express.urlencoded({ extended: true }));

// Validate Twilio signature to prevent spoofed webhooks
function validateTwilioSignature(req) {
  const signature = req.headers['x-twilio-signature'];
  const url = `https://your-domain.com${req.originalUrl}`;
  const data = Object.keys(req.body)
    .sort()
    .reduce((acc, key) => acc + key + req.body[key], '');

  const expectedSignature = crypto
    .createHmac('sha1', process.env.TWILIO_AUTH_TOKEN)
    .update(url + data)
    .digest('base64');

  return signature === expectedSignature;
}

app.post('/webhook/twilio/status', async (req, res) => {
  // Return 200 immediately to prevent Twilio retries
  res.status(200).send('OK');

  if (!validateTwilioSignature(req)) {
    console.error('Invalid Twilio signature');
    return;
  }

  const { MessageSid, MessageStatus, ErrorCode, To } = req.body;

  // Process async - don't block webhook response
  setImmediate(async () => {
    try {
      // Update CRM via API (example: HubSpot)
      await fetch(`https://api.hubapi.com/crm/v3/objects/contacts/${contactId}`, {
        method: 'PATCH',
        headers: {
          'Authorization': `Bearer ${process.env.HUBSPOT_TOKEN}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          properties: {
            last_sms_status: MessageStatus,
            last_sms_sid: MessageSid,
            last_sms_error: ErrorCode || 'none',
            last_sms_timestamp: new Date().toISOString()
          }
        })
      });
    } catch (error) {
      console.error('CRM update failed:', error);
      // Log to dead letter queue for retry
    }
  });
});

app.listen(3000);
Enter fullscreen mode Exit fullscreen mode

Step 3: Handle Phone Number Validation

Add a Zapier Filter step before calling Twilio. Use this regex to validate E.164 format:

// Zapier Filter Configuration
// Only continue if: {{contact.phone}} matches pattern
const e164Regex = /^\+[1-9]\d{1,14}$/;

// This prevents Twilio HTTP 400 errors for invalid numbers
// Common failures: missing +, leading zeros, non-numeric chars
Enter fullscreen mode Exit fullscreen mode

Step 4: Configure Make.com Workflow (Alternative)

In Make, create this flow:

  1. CRM Webhook Module → Trigger on "Deal Updated"
  2. HTTP Module → POST to Twilio API (same payload as Step 1)
  3. Router Module → Split on HTTP status code
    • Success (200-299): Update CRM with MessageSid
    • Failure (400-499): Log to Google Sheets for manual review
  4. Webhook Response Module → Listen for Twilio callbacks

Make's advantage: you can see the entire data flow in one canvas, which cuts debugging time when phone numbers fail validation.

Error Handling & Edge Cases

Twilio returns HTTP 400 for invalid phone numbers. The error payload includes code: 21211 (invalid To number). Catch this in Zapier using an error path:

// Zapier Error Handler
// If Twilio returns 4xx, extract error details:
const errorResponse = {
  code: 21211,
  message: "The 'To' number +1555123 is not a valid phone number.",
  more_info: "https://www.twilio.com/docs/errors/21211"
};

// Log to Google Sheets with: contact_id, phone, error_code, timestamp
// This saved me when 30% of contacts had malformed numbers
Enter fullscreen mode Exit fullscreen mode

Race condition I hit: If two CRM events fire within 100ms (e.g., deal updated twice), Zapier sends duplicate SMS. Fix: add a 500ms delay step and check CRM activity log for recent SMS sends:

// Zapier Filter Step
// Only continue if: last_sms_timestamp is older than 60 seconds
const lastSent = new Date(contact.last_sms_timestamp);
const now = new Date();
const diffSeconds = (now - lastSent) / 1000;

// Skip if SMS sent in last 60s
if (diffSeconds < 60) {
  return false;  // Zapier stops here
}
Enter fullscreen mode Exit fullscreen mode

Webhook timeout issue: If your CRM API takes >4s to respond, Twilio's webhook times out. Solution: use a job queue (Bull, BullMQ

System Diagram

Call flow showing how Zapier handles user input, webhook events, and responses.

sequenceDiagram
    participant User
    participant ZapierApp
    participant TriggerApp
    participant ActionApp
    participant ErrorHandler
    User->>ZapierApp: Create Zap
    ZapierApp->>TriggerApp: Poll for new data
    alt Data Found
        TriggerApp->>ZapierApp: New data available
        ZapierApp->>ActionApp: Execute Action
        ActionApp->>ZapierApp: Action completed
        ZapierApp->>User: Notify success
    else No Data
        TriggerApp->>ZapierApp: No new data
    end
    alt Action Error
        ActionApp->>ErrorHandler: Error occurred
        ErrorHandler->>ZapierApp: Log error
        ZapierApp->>User: Notify error
    end
Enter fullscreen mode Exit fullscreen mode

Testing & Validation

Most Twilio-CRM integrations fail in production because devs skip local testing. Here's how to catch issues before they hit your users.

Local Testing with ngrok

Twilio webhooks require public URLs. Use ngrok to expose your local server:

// server.js - Test webhook handler locally
const express = require('express');
const crypto = require('crypto');
const app = express();

app.use(express.urlencoded({ extended: false }));

app.post('/webhook/sms', (req, res) => {
  const signature = req.headers['x-twilio-signature'];
  const url = `https://your-ngrok-url.ngrok.io/webhook/sms`;

  // Validate signature (use validateTwilioSignature from previous section)
  const expectedSignature = crypto
    .createHmac('sha1', process.env.TWILIO_AUTH_TOKEN)
    .update(url + JSON.stringify(req.body))
    .digest('base64');

  if (signature !== expectedSignature) {
    return res.status(403).json({ 
      failed: true, 
      code: 'INVALID_SIGNATURE',
      message: 'Webhook validation failed' 
    });
  }

  console.log('SMS received:', req.body);
  res.status(200).send('<Response></Response>');
});

app.listen(3000, () => console.log('Webhook server running on port 3000'));
Enter fullscreen mode Exit fullscreen mode

Run ngrok http 3000, then update your Twilio webhook URL to the ngrok domain. Send a test SMS to your Twilio number. Check your terminal for the webhook payload.

Webhook Validation

Critical: Always validate the x-twilio-signature header. Without this, attackers can spam your endpoint. The signature proves the request came from Twilio, not a bot.

Test signature validation by intentionally using the wrong TWILIO_AUTH_TOKEN. Your handler should return 403 with code: 'INVALID_SIGNATURE'. If it doesn't, your validation logic is broken.

Real-World Example

Barge-In Scenario

Most CRM-SMS integrations break when a customer replies mid-automation. Here's what actually happens in production:

The Problem: Your Zapier workflow sends an appointment reminder at 9:00 AM. Customer replies "CANCEL" at 9:00:03 AM. Your Make.com workflow triggers a follow-up SMS at 9:00:05 AM because it's still processing the original event queue. Customer receives conflicting messages.

// Production-grade deduplication handler
const lastSent = new Map(); // Track last message timestamp per customer

app.post('/webhook/sms-received', (req, res) => {
  const { From, Body } = req.body;
  const now = Date.now();

  // Prevent duplicate processing within 30s window
  if (lastSent.has(From)) {
    const diffSeconds = (now - lastSent.get(From)) / 1000;
    if (diffSeconds < 30) {
      console.warn(`Duplicate SMS from ${From} within ${diffSeconds}s`);
      return res.status(200).send('OK'); // ACK but don't process
    }
  }

  lastSent.set(From, now);

  // Cancel pending workflows in Zapier/Make
  fetch('https://hooks.zapier.com/hooks/catch/YOUR_HOOK/cancel', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ 
      customerPhone: From,
      action: 'CANCEL_PENDING',
      timestamp: now 
    })
  }).catch(e => console.error('Zapier cancel failed:', e));

  res.status(200).send('OK');
});
Enter fullscreen mode Exit fullscreen mode

Event Logs

Real webhook payload from Twilio when customer interrupts:

{
  "MessageSid": "SM9f8e7d6c5b4a3",
  "From": "+15551234567",
  "To": "+15559876543",
  "Body": "CANCEL",
  "MessageStatus": "received",
  "NumMedia": "0",
  "EventType": "onMessageReceived"
}
Enter fullscreen mode Exit fullscreen mode

What breaks: Zapier's default 5-second timeout. If your CRM lookup takes 6 seconds, Zapier retries the webhook → duplicate SMS sent.

Fix: Return 200 OK immediately, process async:

app.post('/webhook/sms-received', async (req, res) => {
  res.status(200).send('OK'); // ACK immediately

  // Process async - no timeout risk
  setImmediate(async () => {
    try {
      const crmData = await fetch('https://crm.example.com/api/customer', {
        method: 'POST',
        headers: { 'Authorization': `Bearer ${process.env.CRM_API_KEY}` },
        body: JSON.stringify({ phone: req.body.From })
      });
      // Handle CRM update...
    } catch (e) {
      console.error('CRM update failed:', e);
    }
  });
});
Enter fullscreen mode Exit fullscreen mode

Edge Cases

Multiple rapid replies (customer sends "CANCEL" 3 times in 2 seconds): Use the lastSent Map above. Without it, you'll trigger 3 separate Zapier workflows → 3 cancellation confirmations sent.

False positives (customer replies "ok" vs "OK" vs "Ok"): Normalize input before routing:

const intent = Body.trim().toUpperCase();
if (['CANCEL', 'STOP', 'UNSUBSCRIBE'].includes(intent)) {
  // Trigger cancellation workflow
}
Enter fullscreen mode Exit fullscreen mode

Webhook signature validation failures (30% of production issues): Twilio sends X-Twilio-Signature header. ALWAYS validate:

function validateTwilioSignature(req) {
  const signature = req.headers['x-twilio-signature'];
  const url = `https://${req.headers.host}${req.url}`;
  const data = req.body;

  const expectedSignature = crypto
    .createHmac('sha1', process.env.TWILIO_AUTH_TOKEN)
    .update(Buffer.from(url + Object.keys(data).sort().map(key => key + data[key]).join(''), 'utf-8'))
    .digest('base64');

  return signature === expectedSignature;
}
Enter fullscreen mode Exit fullscreen mode

Cost blowup: Customer in retry loop sends 50 SMS in 1 minute. Rate-limit per phone number:

const rateLimits = new Map();

if (rateLimits.has(From) && rateLimits.get(From) > 5) {
  return res.status(429).send('Rate limit exceeded');
}
rateLimits.set(From, (rateLimits.get(From) || 0) + 1);
setTimeout(() => rateLimits.delete(From), 60000); // Reset after 1min
Enter fullscreen mode Exit fullscreen mode

Common Issues & Fixes

Race Conditions in Webhook Processing

Most Twilio-CRM integrations break when webhooks arrive faster than your server processes them. This happens when a customer sends 3 SMS messages in 2 seconds, but your CRM API takes 800ms per write. Result: duplicate records, out-of-order updates, or lost messages.

// WRONG: No queue, race condition guaranteed
app.post('/webhook/sms', async (req, res) => {
  const messagePayload = req.body;
  await crmAPI.createContact(messagePayload); // 800ms
  res.status(200).send('OK');
});

// CORRECT: Queue with deduplication
const processingQueue = new Map();

app.post('/webhook/sms', async (req, res) => {
  const messagePayload = req.body;
  const messageId = messagePayload.MessageSid;

  // Immediate 200 response to Twilio (required within 10s)
  res.status(200).send('OK');

  // Deduplicate: skip if already processing this message
  if (processingQueue.has(messageId)) {
    console.log(`Duplicate webhook for ${messageId}, skipping`);
    return;
  }

  processingQueue.set(messageId, Date.now());

  try {
    await crmAPI.createContact(messagePayload);
  } catch (error) {
    console.error(`CRM write failed for ${messageId}:`, error);
    // Retry logic here
  } finally {
    processingQueue.delete(messageId);
  }
});
Enter fullscreen mode Exit fullscreen mode

Why this breaks: Twilio retries webhooks if you don't respond within 10 seconds. If your CRM write takes 12 seconds, Twilio sends the same webhook again. Without deduplication, you create duplicate contacts.

Webhook Signature Validation Failures

Production issue: 15% of webhooks fail signature validation on Railway/Heroku due to body-parser middleware corrupting the raw body. Twilio's validateTwilioSignature function needs the EXACT raw POST body, but express.json() parses it first.

// WRONG: Body already parsed, signature fails
app.use(express.json());
app.post('/webhook/sms', (req, res) => {
  const signature = req.headers['x-twilio-signature'];
  const url = `https://yourdomain.com/webhook/sms`;
  const isValid = validateTwilioSignature(signature, url, req.body); // FAILS
});

// CORRECT: Preserve raw body for validation
app.post('/webhook/sms', 
  express.urlencoded({ extended: false }), // Twilio sends form-encoded
  (req, res) => {
    const signature = req.headers['x-twilio-signature'];
    const url = `https://yourdomain.com${req.originalUrl}`;

    // Reconstruct raw body from parsed form data
    const data = req.body;
    const isValid = validateTwilioSignature(signature, url, data);

    if (!isValid) {
      return res.status(403).json({ 
        failed: true, 
        code: 'INVALID_SIGNATURE',
        message: 'Webhook signature validation failed'
      });
    }

    // Process webhook
    res.status(200).send('OK');
  }
);
Enter fullscreen mode Exit fullscreen mode

Critical: Use express.urlencoded() NOT express.json() for Twilio webhooks. Twilio sends application/x-www-form-urlencoded, not JSON.

Rate Limiting CRM API Calls

Salesforce limits you to 15,000 API calls per 24 hours. If you process 50 SMS/minute during business hours, you hit the limit by 2pm. Solution: batch writes every 30 seconds instead of real-time.

const rateLimits = {
  buffer: [],
  flushInterval: 30000, // 30 seconds
  maxBatchSize: 100
};

app.post('/webhook/sms', (req, res) => {
  const messagePayload = req.body;
  res.status(200).send('OK');

  rateLimits.buffer.push(messagePayload);

  if (rateLimits.buffer.length >= rateLimits.maxBatchSize) {
    flushToCRM();
  }
});

setInterval(flushToCRM, rateLimits.flushInterval);

async function flushToCRM() {
  if (rateLimits.buffer.length === 0) return;

  const batch = rateLimits.buffer.splice(0, rateLimits.maxBatchSize);
  try {
    await crmAPI.bulkCreate(batch); // Single API call for 100 records
  } catch (error) {
    console.error('Batch write failed:', error);
    rateLimits.buffer.unshift(...batch); // Re-queue on failure
  }
}
Enter fullscreen mode Exit fullscreen mode

This reduces API calls by 100x (1 call per 100 messages vs. 100 calls).

Complete Working Example

Most tutorials stop at theory. Here's the full production server that handles Twilio webhooks, validates signatures, rate-limits SMS sends, and pushes data to your CRM—all in one file.

This example combines Zapier's visual workflow builder with a lightweight Express server for webhook validation and custom logic that Zapier can't handle (like signature verification and rate limiting).

Full Server Code

const express = require('express');
const crypto = require('crypto');
const fetch = require('node-fetch');

const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Rate limiting state (in-memory - use Redis in production)
const rateLimits = new Map();
const processingQueue = [];
let isProcessing = false;

// Validate Twilio webhook signature
function validateTwilioSignature(url, data, signature) {
  const authToken = process.env.TWILIO_AUTH_TOKEN;
  const params = Object.keys(data)
    .sort()
    .map(key => `${key}${data[key]}`)
    .join('');

  const expectedSignature = crypto
    .createHmac('sha1', authToken)
    .update(url + params)
    .digest('base64');

  return expectedSignature === signature;
}

// Flush batched messages to CRM via Zapier webhook
async function flushToCRM(batch) {
  if (batch.length === 0) return;

  try {
    const response = await fetch(process.env.ZAPIER_WEBHOOK_URL, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        action: 'batch_sms_received',
        messages: batch,
        timestamp: Date.now()
      })
    });

    if (!response.ok) {
      console.error(`CRM sync failed: ${response.status}`);
      // Implement retry logic here
    }
  } catch (error) {
    console.error('CRM flush error:', error.message);
  }
}

// Twilio SMS webhook handler
app.post('/webhook/twilio/sms', async (req, res) => {
  const signature = req.headers['x-twilio-signature'];
  const url = `${process.env.SERVER_URL}/webhook/twilio/sms`;

  // Signature validation prevents spoofed webhooks
  if (!validateTwilioSignature(url, req.body, signature)) {
    return res.status(403).json({
      error: 'Invalid signature',
      code: 'SIGNATURE_MISMATCH'
    });
  }

  const { From, Body, MessageSid } = req.body;
  const messageId = MessageSid;

  // Rate limiting: max 1 SMS per number per 60 seconds
  const lastSent = rateLimits.get(From);
  const now = Date.now();

  if (lastSent) {
    const diffSeconds = (now - lastSent) / 1000;
    if (diffSeconds < 60) {
      return res.status(429).json({
        error: 'Rate limit exceeded',
        retry_after: Math.ceil(60 - diffSeconds)
      });
    }
  }

  rateLimits.set(From, now);

  // Queue message for batch processing
  processingQueue.push({
    messageId,
    from: From,
    body: Body,
    received: now
  });

  // Respond immediately to Twilio (must respond within 15s)
  res.status(200).send('<?xml version="1.0" encoding="UTF-8"?><Response></Response>');

  // Flush queue if batch size reached
  if (processingQueue.length >= 10) {
    const batch = processingQueue.splice(0, 10);
    flushToCRM(batch);
  }
});

// Zapier trigger endpoint: fetch pending messages
app.get('/api/messages/pending', (req, res) => {
  const apiKey = req.headers['x-api-key'];

  if (apiKey !== process.env.API_KEY) {
    return res.status(401).json({ error: 'Unauthorized' });
  }

  // Return queued messages for Zapier polling trigger
  res.json({
    messages: processingQueue.slice(0, 100),
    count: processingQueue.length
  });
});

// Periodic flush for remaining messages
setInterval(() => {
  if (processingQueue.length > 0) {
    const batch = processingQueue.splice(0, processingQueue.length);
    flushToCRM(batch);
  }
}, 30000); // Flush every 30 seconds

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Twilio webhook server running on port ${PORT}`);
});
Enter fullscreen mode Exit fullscreen mode

Run Instructions

Environment Variables (Railway/Heroku):

TWILIO_AUTH_TOKEN=your_twilio_auth_token
ZAPIER_WEBHOOK_URL=https://hooks.zapier.com/hooks/catch/xxxxx/
SERVER_URL=https://your-app.railway.app
API_KEY=generate_random_32_char_string
PORT=3000
Enter fullscreen mode Exit fullscreen mode

Deploy to Railway:

npm init -y
npm install express node-fetch crypto
railway login
railway init
railway up
Enter fullscreen mode Exit fullscreen mode

Connect to Zapier:

  1. Create Zap: Webhooks by Zapier (Catch Hook) → CRM (Create Contact)
  2. Copy webhook URL to ZAPIER_WEBHOOK_URL
  3. In Twilio Console → Phone Numbers → Configure webhook: https://your-app.railway.app/webhook/twilio/sms
  4. Test: Send SMS to your Twilio number, verify CRM contact created

Why This Works: Twilio webhooks hit your server (signature validated), messages queue in-memory, batch flush to Zapier every 30s or when 10 messages accumulate. Rate limiting prevents spam. Zapier handles CRM writes without custom API code.

FAQ

Technical Questions

How do I validate incoming Twilio webhooks in Zapier or Make without writing custom code?

Both platforms handle signature validation natively. In Zapier, use the "Webhooks by Zapier" trigger with Twilio's webhook format—it automatically parses MessageSid, From, To, and Body. In Make, the Twilio module validates signatures server-side before passing data to your workflow. If you're deploying custom code on Railway, manually validate using HMAC-SHA1 with your Twilio Auth Token. The signature header (X-Twilio-Signature) must match the expected value computed from the request URL and body.

What's the difference between using Zapier's native Twilio integration vs. Make's Twilio connector?

Zapier's Twilio app is tightly integrated—you get pre-built actions like "Send SMS" and "Create Call" with minimal configuration. Make offers similar connectors but requires more manual setup for complex workflows. Zapier scales better for simple SMS-to-CRM flows (under 1,000 messages/day). Make excels when you need conditional logic, multiple API calls per message, or custom data transformations. For high-volume SMS (10,000+/day), both hit rate limits—you'll need Railway or a dedicated backend.

Can I send SMS directly from my CRM using Zapier without Twilio?

No. Your CRM (Salesforce, HubSpot, Pipedrive) doesn't have native SMS sending—Twilio is the carrier. Zapier bridges them: CRM trigger → Zapier → Twilio API → SMS delivery. Make works identically. The workflow is: new contact in CRM → Zapier detects change → calls Twilio's Programmable Messaging API → SMS sent.

Performance

How many SMS messages can Zapier handle per minute?

Zapier's free tier: 100 tasks/month. Paid tiers: 750–7,500 tasks/month depending on plan. Each SMS = 1 task. For high volume (1,000+ SMS/day), Zapier becomes expensive. Make has similar limits. Both platforms queue tasks—expect 5–30 second delays during peak hours. For real-time SMS, deploy Railway with direct Twilio API calls (no queue delays).

What causes SMS delivery delays in Zapier workflows?

Three culprits: (1) Zapier's task queue (5–30s backlog during peaks), (2) CRM API latency (Salesforce queries can take 2–5s), (3) Twilio's carrier routing (typically <1s, but can spike to 5s on congested networks). Total latency: 10–40 seconds typical. If you need <5s delivery, use Railway with direct Twilio API calls and async processing.

Platform Comparison

Should I use Zapier, Make, or a custom Railway backend?

Zapier: Best for <500 SMS/day, non-technical users, simple CRM-to-SMS flows. Cost: $20–50/month + Twilio charges.

Make: Better for complex workflows (multiple conditions, data transforms), 500–5,000 SMS/day. Cost: $10–30/month + Twilio charges.

Railway: Required for >5,000 SMS/day, real-time delivery (<5s), custom logic, or cost optimization. You control scaling and pay only for compute used. Requires developer setup.

Can I use both Zapier and Make in the same workflow?

Technically yes, but don't. It creates redundant API calls, duplicate messages, and billing waste. Pick one platform for your primary workflow. Use the other only if you need a fallback (e.g., Zapier fails → Make retries). This adds complexity—avoid unless necessary.

Resources

Railway: Deploy on Railway → https://railway.com?referralCode=ypXpaB

Official Documentation

Integration Guides

Deployment & Hosting

  • Railway Documentation – Node.js deployment, environment variables, webhook tunneling
  • ngrok – Local webhook testing via secure tunnels

Top comments (0)