Як тестувати вебхуки Twilio для SMS і дзвінків

Спрямовуємо номер Twilio на адресу-перехоплювач, читаємо form-encoded параметри, відповідаємо TwiML без власного сервера і перевіряємо X-Twilio-Signature через офіційні бібліотеки.

Оновлено:

Коли на ваш номер Twilio приходить SMS або дзвінок, Twilio надсилає HTTP-запит на вебхук і чекає у відповідь інструкцій у TwiML — це XML-формат. Цим Twilio відрізняється від більшості провайдерів: важливий не лише статус, а й вміст відповіді. Покажемо, як побачити, що саме надсилає Twilio, повернути валідну TwiML-відповідь просто з адреси-перехоплювача і перевірити підпис у власному коді.

Крок 1: створіть адресу для перехоплення

Відкрийте Webhook Inspector і створіть адресу, наприклад:

https://webhook.voronyuk.com/abcd23efgh/twilio/sms

Будь-який підшлях теж ловиться, тож на одній адресі можна тримати .../twilio/sms для повідомлень і .../twilio/voice для дзвінків.

Крок 2: налаштуйте TwiML-відповідь

Стандартна відповідь {"ok":true,"request_id":N} — це не TwiML, а Twilio потрібен XML, щоб зрозуміти, що робити. У налаштуваннях власної відповіді для адреси вкажіть:

  • Статус: 200
  • Заголовок: Content-Type: text/xml
  • Тіло:
<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Message>Hi!</Message>
</Response>

Тепер на кожне вхідне SMS у відповідь піде «Hi!», і жодного власного сервера не треба. Для дзвінків тіло може бути таким:

<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Say>Hello from the webhook test.</Say>
</Response>

Відповідь статична, тобто однакова для всіх запитів. Цього досить, щоб переконатися, що номер підключено, і подивитися на вхідні параметри. Пересилання (крок 5) запускає й ваш код, але Twilio все одно отримує цю статичну TwiML-відповідь, тож для динамічних відповідей потрібен інший підхід — див. нижче.

Крок 3: налаштуйте номер телефону

  1. У Twilio Console відкрийте свої номери й виберіть активний номер.
  2. У налаштуваннях повідомлень знайдіть A message comes in, виберіть Webhook, вставте https://webhook.voronyuk.com/abcd23efgh/twilio/sms і вкажіть HTTP POST.
  3. Для дзвінків так само налаштуйте A call comes in з адресою .../twilio/voice.
  4. Збережіть.

Якщо номер прив’язаний до Messaging Service, вхідні повідомлення можуть оброблятися за налаштуваннями вебхука самого сервісу. Якщо нічого не приходить, перевірте там.

Крок 4: роздивіться параметри

Надішліть SMS на номер. Запит одразу з’явиться в інспекторі. Twilio шле дані як application/x-www-form-urlencoded, а інспектор розкодовує їх у зрозумілі поля:

Параметр Приклад Що означає
MessageSid SM... Унікальний ID повідомлення
AccountSid AC... Ваш акаунт
From +380501234567 Номер відправника
To +15005550006 Ваш номер Twilio
Body привіт Текст повідомлення
NumMedia 0 Кількість вкладених медіафайлів

У запитах про дзвінки є CallSid, From, To, CallStatus і Direction. І в кожному запиті видно заголовок X-Twilio-Signature.

Як працює X-Twilio-Signature

Twilio підписує кожен запит Auth Token вашого акаунта:

  1. Береться повна адреса, на яку звертався Twilio, разом із query-рядком.
  2. Для POST-запиту параметри сортуються за назвою в алфавітному порядку, і кожна пара «назва + значення» дописується до адреси без роздільників.
  3. Від цього рядка рахується HMAC-SHA1 з Auth Token як ключем.
  4. Результат кодується в base64.

Писати це вручну майже ніколи не доводиться: офіційні бібліотеки роблять усе самі, включно з порівнянням за сталий час.

Найчастіше спотикаються на адресі. Twilio підписав ту адресу, яку викликав, тобто публічну адресу перехоплювача, а не http://localhost:3000/sms. Перевіряючи переслані запити, передавайте саме публічну адресу.

Node.js

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

const AUTH_TOKEN = process.env.TWILIO_AUTH_TOKEN;
const PUBLIC_URL = 'https://webhook.voronyuk.com/abcd23efgh/twilio/sms';
const app = express();

app.post('/sms', express.urlencoded({ extended: false }), (req, res) => {
  const signature = req.get('X-Twilio-Signature');
  if (!twilio.validateRequest(AUTH_TOKEN, signature, PUBLIC_URL, req.body)) {
    return res.status(403).send('Invalid signature');
  }
  const twiml = new twilio.twiml.MessagingResponse();
  twiml.message(`Ви написали: ${req.body.Body}`);
  res.type('text/xml').send(twiml.toString());
});

app.listen(3000);

Python

import os

from flask import Flask, Response, abort, request
from twilio.request_validator import RequestValidator
from twilio.twiml.messaging_response import MessagingResponse

app = Flask(__name__)
validator = RequestValidator(os.environ["TWILIO_AUTH_TOKEN"])
PUBLIC_URL = "https://webhook.voronyuk.com/abcd23efgh/twilio/sms"


@app.post("/sms")
def sms():
    signature = request.headers.get("X-Twilio-Signature", "")
    if not validator.validate(PUBLIC_URL, request.form, signature):
        abort(403)
    reply = MessagingResponse()
    reply.message(f"Ви написали: {request.form.get('Body', '')}")
    return Response(str(reply), mimetype="text/xml")


if __name__ == "__main__":
    app.run(port=3000)

Крок 5: пересилання у застосунок

У панелі Пересилання вкажіть ціль http://localhost:3000. Підшлях дописується до цілі, тож .../abcd23efgh/twilio/sms прийде на http://localhost:3000/sms. Запустіть CLI-агент:

WI_TOKEN='<токен>' inspector listen https://webhook.voronyuk.com/abcd23efgh/twilio --to localhost:3000

Точну команду з токеном скопіюйте з панелі. Relay через браузер теж підійде, якщо сервер дозволяє CORS. Чим вони відрізняються — у гайді як тестувати вебхуки локально.

Пересилання одностороннє: застосунок отримує кожен запит, але Twilio завжди бачить відповідь інспектора, а не TwiML вашого застосунку. Щоб під час тестування відповідати динамічно, надсилайте відповідь із застосунку через Messages REST API Twilio, а не через TwiML. Коли логіка готова, вкажіть для номера публічну адресу самого застосунку й повертайте TwiML напряму.

Поки допрацьовуєте код, повторюйте перехоплені повідомлення з тестера вебхуків Twilio, а не пишіть на номер щоразу. Повтори зручні для перевірки парсингу, а підпис пройде лише тоді, коли адреса й параметри не змінилися.

Схожі гайди: вебхук Telegram-бота і Slack Events API.

Часті питання

Як протестувати SMS-вебхук Twilio без сервера?

Вкажіть адресу перехоплювача у полі «A message comes in» для номера, а для самої адреси задайте власну відповідь із Content-Type: text/xml і TwiML у тілі. Ви бачитимете кожне вхідне повідомлення, а відправник отримає відповідь.

Чому Twilio вважає відповідь вебхука невалідною?

Twilio чекає TwiML. JSON у тілі чи неправильний Content-Type не спрацюють. Повертайте XML з кореневим <Response> і Content-Type: text/xml.

Чому не проходить перевірка X-Twilio-Signature?

Зазвичай адреса не збігається з тією, яку викликав Twilio: наприклад, після пересилання ви підставили localhost або загубили query-рядок. Перевіряйте з точною публічною адресою й оригінальними POST-параметрами.

Які параметри Twilio надсилає для вхідного SMS?

Зокрема MessageSid, AccountSid, From, To, Body і NumMedia у вигляді form-encoded полів.

Спробуйте зараз

Отримайте безкоштовний URL для вебхуків і дивіться, як приходять запити, — без реєстрації.

Отримати URL для вебхуків

Інші гайди

Localhost

Як тестувати вебхуки локально

Чому Stripe, GitHub та інші сервіси не бачать ваш localhost і як зловити справжній вебхук на публічній адресі, а потім переслати його собі на машину.

GitHub

Як тестувати вебхуки GitHub

Налаштовуємо вебхук репозиторію, розбираємо заголовки GitHub, правильно перевіряємо X-Hub-Signature-256 і повторно доставляємо події під час налагодження.

MCP

Тестування вебхуків із Claude і ChatGPT через MCP

Підключіть Webhook Inspector як MCP-сервер, і AI-асистент сам створить URL, дочекається вебхука й розбере його разом із вами.

Shopify

Як тестувати вебхуки Shopify

Створюємо вебхук магазину або підписку застосунку, розбираємо заголовки X-Shopify, перевіряємо X-Shopify-Hmac-Sha256 і враховуємо тайм-аути та повтори Shopify.

Slack

Як тестувати Slack Events API та interactivity

Дивимося, що Slack надсилає на Request URL, проходимо перевірку url_verification, валідуємо X-Slack-Signature і розкодовуємо form-encoded payload від interactivity.

Stripe

Як тестувати вебхуки Stripe

Налаштовуємо ендпоінт у Stripe, дивимося, що саме приходить, перевіряємо Stripe-Signature на Node.js і Python та пересилаємо події на локальний застосунок.

Telegram

Як тестувати вебхук Telegram-бота

Спрямовуємо бота на публічну адресу через setWebhook, роздивляємося сирі апдейти від Telegram і пересилаємо їх боту, що працює на вашій машині.