Коллбэки и webhooks

Что такое webhook

Webhook — механизм получения уведомлений об определённых событиях на TimePad (в основном о действиях пользователей) на свой собственный сайт.

Например, на TimePad есть webhook, который позволяет отслеживать изменения статусов регистрации пользователей. Он вызывается каждый раз при изменении любого билета вашей организации. По сути происходит вот что: данные регистрации кладутся в JSON и отправляются на URL, указаный в настройках организации.

Как использовать webhook

Для использования достаточно прописать в настройках организации URL, на который будет отправляться POST-запрос.

image

В поле Ссылка для уведомления нужно оставить адрес, к которому TimePad будет обращаться в случае смены статусов билетов, а в поле Секретная фраза — ключ, которым будет подписано сообщение от TimePad. И не забудьте нажать «Сохранить».

После этого при любой смене статуса билета в вашей организации — например, при регистрации или оплате — будет выполняться POST-запрос на адрес, который вы указали в настройках.

Как прочитать webhook

Timepad посылает хук методом POST с JSON-объектом в теле запроса. Также мы подписываем хуки с помощью hmac, что позволяет вашим приложениям убедиться, что данные посылаем именно мы.

Пример чтения webhook'а в PHP

<?php
// Читаем содержимое POST-запроса
$body      = @file_get_contents("php://input");
// Генерируем код, которым должен быть подписан webhook
$sha1      = hash_hmac('sha1', $body, 'SuperSecret');

// Сравниваем полученый код подписи, с тем что пришёл с хуком
if ("sha1={$sha1}" == $_SERVER['HTTP_X_HUB_SIGNATURE']) {
    // webhook пришёл от сервиса Timepad
    print_r(json_decode($body, true));
}
?>

Условия вызова хука изменения статусов билетов

Вебхук об изменении статуса посылается при любом изменении статуса билета, а именно:

  • При создании заказа (в том числе бронировании для платных билетов и подачи заявки на участие при включенном режиме "Прием заявок")
  • Оплата билета
  • Возврат денег за оплаченный билет (в момент установки метки "Возвращен" для заказа)
  • Отказ от билета покупателем (в том числе отказ от забронированного билета)
  • Истечение срока брони билета
  • Отклонение или приём заявки на участие в событии
  • Удаление билета

Содержание JSON запроса для хука изменения статусов билетов

{
  "id": "5184211:83845994", // ID билета (печататется на самом билете)
  "event_id": 215813, // ID мероприятия
  "organization_id": 29963, // ID организации, создавшей мероприятие
  "order_id": "4955686", // ID заказа
  "reg_date": "2015-07-24 19:04:37", // Дата заказа билета
  "reg_id": 361138, // Внутренний ID билета
  "status": "забронировано", // Статус заказа / билета
  "status_raw": "booked", // Статус заказа в машиночитаемом формате
  "email": "test-mail@ya.ru", // E-mail заказчика
  "surname": "Смирнов", // Фамилия на билете
  "name": "Владимир", // Имя на билете
  "attended": false, // Отметка о посещении мероприятия
  "code": "83845994", // Числовой код билета
  "barcode": "1000838459949", // Числовой код в формате EAN-13, напечатан на билете в виде штрих-кода
  "price_nominal": 1000, // Стоимость билета на момент заказа 
  "answers": [ // Список ответов на вопросы анкеты (если есть)
    {
      "id": 889802, // ID вопроса
      "type": "text", // Тип вопроса
      "name": "E-mail", // Текст вопроса
      "mandatory": null, // Обязательность вопроса
      "value": "test-mail@ya.ru" // Текст ответа
    },
    {
      "id": 889803, // ID вопроса
      "type": "text", // Тип вопроса
      "name": "Фамилия", // Текст вопроса
      "mandatory": null, // Обязательность вопроса
      "value": "Смирнов" // Текст ответа
    },
    {
      "id": 889804, // ID вопроса
      "type": "text", // Тип вопроса
      "name": "Имя", // Текст вопроса
      "mandatory": null, // Обязательность вопроса
      "value": "Владимир" // Текст ответа
    }
  ],
  "aux": []
}

Список возможных статусов билетов

Поле status_raw в запросе может содержать следующие значения (в скобках указаны соответствующие значения поля status)

  • ok (ок): бесплатный билет успешно заказан
  • paid (оплачено): платный билет успешно оплачен он-лайн
  • booked (забронировано): билет находится в статусе "Забронировано"
  • notpaid (просрочено): билет не был оплачен и срок брони для него истек
  • inactive (отказ): участник отказался от участия
  • deleted (удалено): организатор удалил билет
  • returned (возврат): участнику были возвращены деньги за билет
  • pending (заявка рассматривается): заявка на участие в событии находится на рассмотрении
  • rejected (отклонено): заявка на участие в событии была отклонена
  • booked_offline (бронь для выкупа): билет был заказан для выкупа в офисе организатора
  • paid_offline (оплачено на месте): билет был оплачен в офисе организатора
  • paid_ur (оплачено компанией): билет был оплачен юридическим платежом
  • transfer_payment (перенесена оплата): билет был оплачен переносом оплаты с другого заказа
  • return_payment_request (запрос на возврат): участник оплатил билет и запросил возврат средств
  • return_payment_reject (отказ возврата): билет оплачен, участнику отказали в возврате
  • return_org (возврат организатором): билет оплачен, организатор возвращает деньги за билет
  • return_tp (возврат TimePad): билет оплачен, участнику будет сделан возврат

Как отвечать на webhook

Принимающий хуки скрипт в случае успеха должен отвечать http-кодом 200. При получении любого другого кода TimePad считает попытку отправки данных неудачной. Также отправка считается неудачной, если ответа не поступило в течение 5 секунд.

В таких случаях запрос будет отправлен на этот же адрес с теми же данными через час. Если эта попытка тоже не удалась, следующая состоится через сутки. После этого запрос будет удалён из очереди, даже если доставка так и не состоялась. Следите за своими серверами ;)

Проектирование системы, принимающей вебхуки

Разрабатывая систему, принимающую вебхуки, лучше снадбите её логами — какие вебхуки приходили.

Обязательно предусмотрите у себя следующие свойства вебхуков (кстати, все это относится к вебхукам абсолютно любых систем):

Порядок прихода вебхуков не обязательно хронологический

Вебхуки отправляются в множество потоков. Совершенно не исключен тот факт, что какой-то из потоков отправит более поздний вебхук быстрее, чем соседий поток — более ранний. Если нарушение хронологии может вам что-то сильно сломать — проверяйте дату хука (поле hook_generated_at), сравнивайте её с датой последнего хука по той же сущности, который вы приняли.

Вебхук может не прийти вовсе

  • Проблема может случиться на вашей стороне и вебхук не будет обработан
  • Проблема может случиться на нашей стороне (например, вебхук может быть в принципе не отправлен)
  • Проблема может случиться где-то посередине (например, проблемы в сети, блокировки на уровне магистральных провайдеров, сбившиеся таблицы маршрутизации и так далее)

Когда отправитель вебхука видит, что отправленный запрос не завершился корректно (HTTP статусом 200 OK), он его обычно переотправляет (еще одно место где может сбиться хронология). Вторая отправка чаще всего завершается успехом.

Один и тот же вебхук может прийти несколько раз

Достаточно часто бывает так, что принимающая сторона на самом деле обработала вебхук и ответила корректным статусом, но отправитель этого ответа не дождался или интерпретировал его неверно (например, при проблемах сети).

В этом случае отправитель сделает повторную попытку отправки вебхука и принимающая сторона будет обязана обработать его корректно – т.е. проигнорировать, но отправить ответ HTTP 200 OK. Вы можете проверять GUID вебхука (поле hook_guid).

При отправке вебхука есть таймаут

Наш сервер 5 секунд ждет ответа о том, что вебхук обработан. Если вам этого времени может не хватать (много обработки, медленное соединение, высокая нагрузка), используйте механизм очередей — приняв вебхук, ставьте его обработку в очередь и отвечайте, что вебхук принят.