Як тестувати вебхуки 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: налаштуйте номер телефону
- У Twilio Console відкрийте свої номери й виберіть активний номер.
- У налаштуваннях повідомлень знайдіть A message comes in, виберіть Webhook, вставте
https://webhook.voronyuk.com/abcd23efgh/twilio/smsі вкажіть HTTP POST. - Для дзвінків так само налаштуйте A call comes in з адресою
.../twilio/voice. - Збережіть.
Якщо номер прив’язаний до 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 вашого акаунта:
- Береться повна адреса, на яку звертався Twilio, разом із query-рядком.
- Для POST-запиту параметри сортуються за назвою в алфавітному порядку, і кожна пара «назва + значення» дописується до адреси без роздільників.
- Від цього рядка рахується HMAC-SHA1 з Auth Token як ключем.
- Результат кодується в 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 для вебхуків