Вебхуки

Вебхуки

Gemlead отправляет HTTPS-запрос на ваш сервер в тот момент, когда лид найден, становится горячим, отвечает или реагирует на ваше сообщение, а также когда у Sam AI готов ответ, когда он отправляет ответ или передаёт вам диалог. Каждый запрос подписан, чтобы вы знали, что он от нас.

Настройка эндпоинта

Добавьте эндпоинт в приложении, в разделе API, или запросом Создание эндпоинта вебхука. Укажите публичный HTTPS URL и нужные типы событий и сохраните полученный secret, чтобы проверять подписи.

  • Только HTTPS, без учётных данных в URL и без частных, loopback или link-local адресов.
  • До 10 эндпоинтов на аккаунт.
  • filters.persona_ids ограничивает события эндпоинта лидами выбранных персон, для событий любого типа.
  • Отправьте тестовое событие, чтобы проверить сервер до прихода настоящих событий.

Вебхуки входят в тарифы Business и Enterprise. Если аккаунт переходит на тариф Free или Starter, его эндпоинты сохраняются, но ничего не отправляют, пока он не вернётся на Business или Enterprise.

POST/v1/webhook_endpoints
curl -X POST https://gemlead.ai/api/v1/webhook_endpoints \
  -H "Authorization: Bearer $GEMLEAD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/webhooks/gemlead",
    "events": ["lead.hot", "reply.received"],
    "description": "CRM sync",
    "filters": {
      "persona_ids": ["lvWBPnytpS"]
    }
  }'

Типы событий

У всех событий одинаковая внешняя структура. Содержимое data зависит от type.

ТипКогда отправляется
lead.createdGemlead находит и оценивает нового лида, какой бы ни была оценка. Для каждого лида событие отправляется один раз, вместе с первой оценкой, и только в течение 3 дней после того, как лид найден, поэтому переоценка более старого лида его не вызывает. Если лид сразу получает 60 или выше, отправляется ещё и lead.hot.
lead.hotОценка лида впервые достигает 60 или выше. Для каждого лида событие отправляется один раз, при переоценке повторно не отправляется.
reply.receivedЛид присылает вам новое сообщение, в кампании или вне её. Для каждого сообщения событие отправляется один раз, если сообщению меньше 6 часов. Если от лида приходит много сообщений сразу, событие отправляется только для 5 самых новых.
reaction.receivedЛид реагирует эмодзи на одно из ваших сообщений. Событие отправляется один раз для каждого эмодзи лида на сообщении.
sam.suggestion.readyУ Sam AI готов новый ответ, который вам остаётся отправить, в диалоге в режиме draft. Для каждой подсказки событие отправляется один раз, если ей меньше 6 часов.
sam.reply.sentОтвет Sam AI уходит с вашего аккаунта X, сам в режиме auto или после того, как вы одобрите его в приложении. Для каждого ответа событие отправляется один раз, если ему меньше 6 часов. Сообщения, которые вы пишете сами, его никогда не вызывают, их возвращает Получение диалога.
sam.handoffSam AI передаёт вам диалог, если не видит хорошего ответа без вашего участия, если лид прислал только фото или голосовое сообщение или если его ответ не удалось отправить. В режиме auto он после этого оставляет следующие сообщения лида вам. Для каждой передачи событие отправляется один раз, если ей меньше 6 часов.

Объект события

  • idstring
    Уникальный идентификатор события, evt_ и 24 символа. Также передаётся в заголовке Gemlead-Event-Id.
  • objectstring
    Всегда event.
  • typestring
    Тип события.
    lead.createdlead.hotreply.receivedreaction.receivedsam.suggestion.readysam.reply.sentsam.handoff
  • created_atstring
    Время события, ISO 8601.
  • account_idstring
    Аккаунт Gemlead, в котором оно произошло.
  • dataobject
    Содержимое события, которое зависит от его типа.

lead.created

Gemlead находит и оценивает нового лида, какой бы ни была оценка. Для каждого лида событие отправляется один раз, вместе с первой оценкой, и только в течение 3 дней после того, как лид найден, поэтому переоценка более старого лида его не вызывает. Если лид сразу получает 60 или выше, отправляется ещё и lead.hot.

Данные

lead.created
{
  "id": "evt_8kQ2vXr4LmT9wPz1Hc6YbN3d",
  "object": "event",
  "type": "lead.created",
  "created_at": "2026-11-05T14:03:40.117Z",
  "account_id": "a9PzK2vRtM",
  "data": {
    "object": {
      "id": "k3VnQ8wZpR",
      "object": "lead",
      "profile": {
        "x_user_id": "1592047713",
        "username": "mark_picks",
        "name": "Mark Ellis",
        "bio": "Free NBA picks every day. Sports fan first.",
        "followers_count": 2150,
        "following_count": 940,
        "location": "Denver, CO",
        "website": "https://markpicks.net",
        "profile_image_url": "https://pbs.twimg.com/profile_images/1592047713/mark_400x400.jpg",
        "verified": false,
        "url": "https://x.com/mark_picks"
      },
      "score": 38,
      "score_reason": "Shares free picks for fun and sells nothing yet, no sign of a paying community.",
      "hot": false,
      "status": "scored",
      "persona": {
        "id": "lvWBPnytpS",
        "name": "Tipsters"
      },
      "campaign": null,
      "conversation_id": null,
      "locked_to": null,
      "found_at": "2026-11-05T14:02:11.482Z",
      "scored_at": "2026-11-05T14:03:40.117Z",
      "updated_at": "2026-11-05T14:03:40.117Z",
      "metadata": {}
    }
  }
}

lead.hot

Оценка лида впервые достигает 60 или выше. Для каждого лида событие отправляется один раз, при переоценке повторно не отправляется.

Данные

lead.hot
{
  "id": "evt_8kQ2vXr4LmT9wPz1Hc6YbN3d",
  "object": "event",
  "type": "lead.hot",
  "created_at": "2026-11-05T14:03:40.117Z",
  "account_id": "a9PzK2vRtM",
  "data": {
    "object": {
      "id": "msgOtJ8dvT",
      "object": "lead",
      "profile": {
        "x_user_id": "1460283925",
        "username": "jane_builds",
        "name": "Jane Doe",
        "bio": "Runs a paid trading community. Daily setups, no hype.",
        "followers_count": 12400,
        "following_count": 380,
        "location": "Austin, TX",
        "website": "https://janedoe.com",
        "profile_image_url": "https://pbs.twimg.com/profile_images/1460283925/jane_400x400.jpg",
        "verified": true,
        "url": "https://x.com/jane_builds"
      },
      "score": 86,
      "score_reason": "Runs a paid trading community and asks how to collect payments for it.",
      "hot": true,
      "status": "scored",
      "persona": {
        "id": "lvWBPnytpS",
        "name": "Tipsters"
      },
      "campaign": null,
      "conversation_id": null,
      "locked_to": null,
      "found_at": "2026-11-05T14:02:11.482Z",
      "scored_at": "2026-11-05T14:03:40.117Z",
      "updated_at": "2026-11-05T14:03:40.117Z",
      "metadata": {}
    }
  }
}

reply.received

Лид присылает вам новое сообщение, в кампании или вне её. Для каждого сообщения событие отправляется один раз, если сообщению меньше 6 часов. Если от лида приходит много сообщений сразу, событие отправляется только для 5 самых новых.

Данные

  • leadobject
    Лид, который написал.
  • messageobject
    Новое сообщение с direction, равным inbound.
  • campaignobject или null
    Кампания, в которой находится лид, с её id, name и status.
  • conversation_idstring
    Используйте его в запросе Получение диалога, чтобы получить всю переписку.
reply.received
{
  "id": "evt_8kQ2vXr4LmT9wPz1Hc6YbN3d",
  "object": "event",
  "type": "reply.received",
  "created_at": "2026-11-05T14:03:40.117Z",
  "account_id": "a9PzK2vRtM",
  "data": {
    "lead": {
      "id": "msgOtJ8dvT",
      "object": "lead",
      "profile": {
        "x_user_id": "1460283925",
        "username": "jane_builds",
        "name": "Jane Doe",
        "bio": "Runs a paid trading community. Daily setups, no hype.",
        "followers_count": 12400,
        "following_count": 380,
        "location": "Austin, TX",
        "website": "https://janedoe.com",
        "profile_image_url": "https://pbs.twimg.com/profile_images/1460283925/jane_400x400.jpg",
        "verified": true,
        "url": "https://x.com/jane_builds"
      },
      "score": 86,
      "score_reason": "Runs a paid trading community and asks how to collect payments for it.",
      "hot": true,
      "status": "in_conversation",
      "persona": {
        "id": "lvWBPnytpS",
        "name": "Tipsters"
      },
      "campaign": {
        "id": "c7RkQ2wLxe",
        "name": "Tipsters warm intro",
        "status": "in_progress"
      },
      "conversation_id": "msgOtJ8dvT",
      "locked_to": null,
      "found_at": "2026-11-05T14:02:11.482Z",
      "scored_at": "2026-11-05T14:03:40.117Z",
      "updated_at": "2026-11-05T14:03:40.117Z",
      "metadata": {}
    },
    "message": {
      "id": "1985321409876543210",
      "object": "message",
      "direction": "inbound",
      "text": "Yes, I want to charge for my Discord. How does it work?",
      "created_at": "2026-11-06T08:12:44.000Z",
      "media": [],
      "reactions": [],
      "encrypted": false,
      "reply_to_message_id": null
    },
    "campaign": {
      "id": "c7RkQ2wLxe",
      "name": "Tipsters warm intro",
      "status": "in_progress"
    },
    "conversation_id": "msgOtJ8dvT"
  }
}

reaction.received

Лид реагирует эмодзи на одно из ваших сообщений. Событие отправляется один раз для каждого эмодзи лида на сообщении.

Данные

  • leadobject
    Лид, который отреагировал.
  • messageobject
    Ваше сообщение, на которое отреагировал лид, с direction, равным outbound.
  • reactionobject
    Новая реакция с direction, равным inbound.
reaction.received
{
  "id": "evt_8kQ2vXr4LmT9wPz1Hc6YbN3d",
  "object": "event",
  "type": "reaction.received",
  "created_at": "2026-11-05T14:03:40.117Z",
  "account_id": "a9PzK2vRtM",
  "data": {
    "lead": {
      "id": "msgOtJ8dvT",
      "object": "lead",
      "profile": {
        "x_user_id": "1460283925",
        "username": "jane_builds",
        "name": "Jane Doe",
        "bio": "Runs a paid trading community. Daily setups, no hype.",
        "followers_count": 12400,
        "following_count": 380,
        "location": "Austin, TX",
        "website": "https://janedoe.com",
        "profile_image_url": "https://pbs.twimg.com/profile_images/1460283925/jane_400x400.jpg",
        "verified": true,
        "url": "https://x.com/jane_builds"
      },
      "score": 86,
      "score_reason": "Runs a paid trading community and asks how to collect payments for it.",
      "hot": true,
      "status": "in_conversation",
      "persona": {
        "id": "lvWBPnytpS",
        "name": "Tipsters"
      },
      "campaign": {
        "id": "c7RkQ2wLxe",
        "name": "Tipsters warm intro",
        "status": "in_progress"
      },
      "conversation_id": "msgOtJ8dvT",
      "locked_to": null,
      "found_at": "2026-11-05T14:02:11.482Z",
      "scored_at": "2026-11-05T14:03:40.117Z",
      "updated_at": "2026-11-05T14:03:40.117Z",
      "metadata": {}
    },
    "message": {
      "id": "1985329981234567890",
      "object": "message",
      "direction": "outbound",
      "text": "Happy to show you. Do you sell picks monthly or per event?",
      "created_at": "2026-11-06T08:20:03.000Z",
      "media": [],
      "reactions": [
        {
          "emoji": "👍",
          "direction": "inbound",
          "created_at": "2026-11-06T08:24:51.000Z"
        }
      ],
      "encrypted": false,
      "reply_to_message_id": "1985321409876543210"
    },
    "reaction": {
      "emoji": "👍",
      "direction": "inbound",
      "created_at": "2026-11-06T08:24:51.000Z"
    }
  }
}

sam.suggestion.ready

У Sam AI готов новый ответ, который вам остаётся отправить, в диалоге в режиме draft. Для каждой подсказки событие отправляется один раз, если ей меньше 6 часов.

Данные

  • leadobject
    Лид, которому отвечает Sam AI.
  • conversation_idstring
    Используйте его в запросе Получение Sam AI диалога, чтобы снова прочитать подсказку или сменить режим.
  • campaignobject или null
    Кампания, в которой находится лид, с её id, name и status.
  • suggestionobject
    Готовый ответ Sam AI.
sam.suggestion.ready
{
  "id": "evt_8kQ2vXr4LmT9wPz1Hc6YbN3d",
  "object": "event",
  "type": "sam.suggestion.ready",
  "created_at": "2026-11-05T14:03:40.117Z",
  "account_id": "a9PzK2vRtM",
  "data": {
    "lead": {
      "id": "msgOtJ8dvT",
      "object": "lead",
      "profile": {
        "x_user_id": "1460283925",
        "username": "jane_builds",
        "name": "Jane Doe",
        "bio": "Runs a paid trading community. Daily setups, no hype.",
        "followers_count": 12400,
        "following_count": 380,
        "location": "Austin, TX",
        "website": "https://janedoe.com",
        "profile_image_url": "https://pbs.twimg.com/profile_images/1460283925/jane_400x400.jpg",
        "verified": true,
        "url": "https://x.com/jane_builds"
      },
      "score": 86,
      "score_reason": "Runs a paid trading community and asks how to collect payments for it.",
      "hot": true,
      "status": "in_conversation",
      "persona": {
        "id": "lvWBPnytpS",
        "name": "Tipsters"
      },
      "campaign": {
        "id": "c7RkQ2wLxe",
        "name": "Tipsters warm intro",
        "status": "in_progress"
      },
      "conversation_id": "msgOtJ8dvT",
      "locked_to": null,
      "found_at": "2026-11-05T14:02:11.482Z",
      "scored_at": "2026-11-05T14:03:40.117Z",
      "updated_at": "2026-11-05T14:03:40.117Z",
      "metadata": {}
    },
    "conversation_id": "msgOtJ8dvT",
    "campaign": {
      "id": "c7RkQ2wLxe",
      "name": "Tipsters warm intro",
      "status": "in_progress"
    },
    "suggestion": {
      "messages": [
        "It takes two minutes. You set a price, members pay, and they get their Discord role right away.",
        "Want the link to set it up?"
      ],
      "stage": "answer_question",
      "why": "She asked how it works, so the reply explains it in one line and offers the link.",
      "replying_to_message_id": "1985321409876543210",
      "created_at": "2026-11-06T08:13:20.000Z"
    }
  }
}

sam.reply.sent

Ответ Sam AI уходит с вашего аккаунта X, сам в режиме auto или после того, как вы одобрите его в приложении. Для каждого ответа событие отправляется один раз, если ему меньше 6 часов. Сообщения, которые вы пишете сами, его никогда не вызывают, их возвращает Получение диалога.

Данные

  • leadobject
    Лид, которому ответил Sam AI.
  • conversation_idstring
    Используйте его в запросе Получение диалога, чтобы получить всю переписку.
  • campaignobject или null
    Кампания, в которой находится лид, с её id, name и status.
  • messagesмассив strings
    DM, которые отправил Sam AI, по порядку.
  • sent_bystring
    auto, если Sam AI отправил его сам, approved, если вы одобрили его в приложении.
    autoapproved
  • replying_to_message_idstring или null
    Сообщение лида, на которое он ответил.
  • sent_atstring
    Время отправки, ISO 8601.
sam.reply.sent
{
  "id": "evt_8kQ2vXr4LmT9wPz1Hc6YbN3d",
  "object": "event",
  "type": "sam.reply.sent",
  "created_at": "2026-11-05T14:03:40.117Z",
  "account_id": "a9PzK2vRtM",
  "data": {
    "lead": {
      "id": "msgOtJ8dvT",
      "object": "lead",
      "profile": {
        "x_user_id": "1460283925",
        "username": "jane_builds",
        "name": "Jane Doe",
        "bio": "Runs a paid trading community. Daily setups, no hype.",
        "followers_count": 12400,
        "following_count": 380,
        "location": "Austin, TX",
        "website": "https://janedoe.com",
        "profile_image_url": "https://pbs.twimg.com/profile_images/1460283925/jane_400x400.jpg",
        "verified": true,
        "url": "https://x.com/jane_builds"
      },
      "score": 86,
      "score_reason": "Runs a paid trading community and asks how to collect payments for it.",
      "hot": true,
      "status": "in_conversation",
      "persona": {
        "id": "lvWBPnytpS",
        "name": "Tipsters"
      },
      "campaign": {
        "id": "c7RkQ2wLxe",
        "name": "Tipsters warm intro",
        "status": "in_progress"
      },
      "conversation_id": "msgOtJ8dvT",
      "locked_to": null,
      "found_at": "2026-11-05T14:02:11.482Z",
      "scored_at": "2026-11-05T14:03:40.117Z",
      "updated_at": "2026-11-05T14:03:40.117Z",
      "metadata": {}
    },
    "conversation_id": "msgOtJ8dvT",
    "campaign": {
      "id": "c7RkQ2wLxe",
      "name": "Tipsters warm intro",
      "status": "in_progress"
    },
    "messages": [
      "It takes two minutes. You set a price, members pay, and they get their Discord role right away.",
      "Want the link to set it up?"
    ],
    "sent_by": "auto",
    "replying_to_message_id": "1985321409876543210",
    "sent_at": "2026-11-06T08:14:05.000Z"
  }
}

sam.handoff

Sam AI передаёт вам диалог, если не видит хорошего ответа без вашего участия, если лид прислал только фото или голосовое сообщение или если его ответ не удалось отправить. В режиме auto он после этого оставляет следующие сообщения лида вам. Для каждой передачи событие отправляется один раз, если ей меньше 6 часов.

Данные

  • leadobject
    Лид, которому нужно ответить.
  • conversation_idstring
    Используйте его в запросе Получение диалога, чтобы получить всю переписку.
  • campaignobject или null
    Кампания, в которой находится лид, с её id, name и status.
  • reasonstring или null
    Почему Sam AI передаёт диалог вам, в его собственной формулировке.
sam.handoff
{
  "id": "evt_8kQ2vXr4LmT9wPz1Hc6YbN3d",
  "object": "event",
  "type": "sam.handoff",
  "created_at": "2026-11-05T14:03:40.117Z",
  "account_id": "a9PzK2vRtM",
  "data": {
    "lead": {
      "id": "msgOtJ8dvT",
      "object": "lead",
      "profile": {
        "x_user_id": "1460283925",
        "username": "jane_builds",
        "name": "Jane Doe",
        "bio": "Runs a paid trading community. Daily setups, no hype.",
        "followers_count": 12400,
        "following_count": 380,
        "location": "Austin, TX",
        "website": "https://janedoe.com",
        "profile_image_url": "https://pbs.twimg.com/profile_images/1460283925/jane_400x400.jpg",
        "verified": true,
        "url": "https://x.com/jane_builds"
      },
      "score": 86,
      "score_reason": "Runs a paid trading community and asks how to collect payments for it.",
      "hot": true,
      "status": "in_conversation",
      "persona": {
        "id": "lvWBPnytpS",
        "name": "Tipsters"
      },
      "campaign": {
        "id": "c7RkQ2wLxe",
        "name": "Tipsters warm intro",
        "status": "in_progress"
      },
      "conversation_id": "msgOtJ8dvT",
      "locked_to": null,
      "found_at": "2026-11-05T14:02:11.482Z",
      "scored_at": "2026-11-05T14:03:40.117Z",
      "updated_at": "2026-11-05T14:03:40.117Z",
      "metadata": {}
    },
    "conversation_id": "msgOtJ8dvT",
    "campaign": {
      "id": "c7RkQ2wLxe",
      "name": "Tipsters warm intro",
      "status": "in_progress"
    },
    "reason": "She wants a call before she signs up."
  }
}

Доставка

Gemlead отправляет каждое событие как JSON-запрос POST и ждёт вашего ответа 10 секунд. Любой ответ 2xx означает успешную доставку. Редирект, любой другой статус, тайм-аут или ошибка соединения считаются неудачей, и событие отправляется повторно.

ЗаголовокЗначение
Gemlead-SignatureВременная метка и подпись, t=1762444800,v1=5f2b...
Gemlead-Event-Idid события, чтобы пропускать уже обработанные события.
Gemlead-Event-Typetype события.
User-AgentGemlead-Webhooks/1.0
Запрос
POST /webhooks/gemlead HTTP/1.1
Content-Type: application/json; charset=utf-8
User-Agent: Gemlead-Webhooks/1.0
Gemlead-Event-Id: evt_8kQ2vXr4LmT9wPz1Hc6YbN3d
Gemlead-Event-Type: lead.hot
Gemlead-Signature: t=1762444800,v1=58ca5e964b92d617c979d3f067cdc2035a1a1a9d3f848d7d927c2db09b8e9baa

Проверка подписи

Отправить запрос на ваш URL может кто угодно. Прежде чем доверять запросу, проверьте, что он пришёл от Gemlead.

  1. Прочитайте сырое тело в виде байтов, до любого разбора JSON. Разбор и повторная сериализация меняют байты.
  2. Разделите заголовок Gemlead-Signature по запятым. t содержит Unix-время в секундах, v1 содержит подпись.
  3. Вычислите HMAC SHA256 от t, точки и сырого тела с полным секретом эндпоинта в качестве ключа, включая whsec_, и запишите результат в hex.
  4. Сравните результат с каждым v1, используя сравнение за постоянное время.
  5. Отклоняйте запросы, у которых t расходится с вашими часами больше чем на 5 минут, чтобы старый запрос нельзя было воспроизвести. Каждая повторная отправка подписывается с новым t.

Если проверка не прошла, отвечайте 400. Храните секрет в переменной окружения, например GEMLEAD_WEBHOOK_SECRET.

const crypto = require("crypto");
const express = require("express");

// rawBody: the request body exactly as received, before any JSON parsing.
function verifyGemleadSignature(rawBody, header, secret, toleranceSeconds = 300) {
  if (typeof header !== "string") return false;
  let timestamp = null;
  const signatures = [];
  for (const part of header.split(",")) {
    const [key, value] = part.split("=", 2).map((s) => (s || "").trim());
    if (key === "t") timestamp = value;
    if (key === "v1" && value) signatures.push(value);
  }
  if (!/^\d+$/.test(timestamp || "") || signatures.length === 0) return false;
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > toleranceSeconds) return false;
  const expected = crypto.createHmac("sha256", secret).update(`${timestamp}.`).update(rawBody).digest();
  return signatures.some((signature) => {
    const given = Buffer.from(signature, "hex");
    return given.length === expected.length && crypto.timingSafeEqual(given, expected);
  });
}

const app = express();

// The body must stay raw on this route.
app.post("/webhooks/gemlead", express.raw({ type: "application/json" }), (req, res) => {
  const ok = verifyGemleadSignature(
    req.body,
    req.get("Gemlead-Signature"),
    process.env.GEMLEAD_WEBHOOK_SECRET,
  );
  if (!ok) return res.status(400).send("Invalid signature");

  const event = JSON.parse(req.body.toString("utf8"));
  // Already handled? Skip it, retries send the same event.id again.
  if (event.type === "lead.created") {
    // event.data.object is the new lead, hot or not
  } else if (event.type === "lead.hot") {
    // event.data.object is the lead
  } else if (event.type === "reply.received") {
    // event.data.lead, event.data.message, event.data.campaign, event.data.conversation_id
  }
  res.sendStatus(200);
});

app.listen(3000);

Повторные отправки

Если доставка не удалась, Gemlead пробует ещё 7 раз, каждый раз выжидая дольше. После 8-й попытки, примерно через 45 часов после первой, доставка помечается как неудачная.

ПопыткаОтправка
1Сразу
2Через 1 минуту после предыдущей неудачи
3Через 5 минут после предыдущей неудачи
4Через 30 минут после предыдущей неудачи
5Через 2 часа после предыдущей неудачи
6Через 6 часов после предыдущей неудачи
7Через 12 часов после предыдущей неудачи
8Через 24 часа после предыдущей неудачи

Эндпоинт, который не отвечает успешно 3 дня подряд и набрал не меньше 8 неудачных попыток, отключается. Его status меняется на disabled, disabled_reason на failing, а ожидающие доставки завершаются неудачей. Исправьте сервер, затем снова включите эндпоинт в приложении или запросом Изменение эндпоинта вебхука, и отсчёт начнётся заново. Тестовые события отправляются один раз и не учитываются.

Список доставок показывает код статуса и ошибку каждой попытки, чтобы вы видели, что ответил ваш сервер.

Рекомендации

  • Отвечайте 2xx в течение 10 секунд, а саму работу выполняйте в очереди.
  • Доставка выполняется как минимум один раз. Используйте id события, который также есть в Gemlead-Event-Id, чтобы пропускать уже обработанные события.
  • События могут приходить не по порядку. Сравнивайте created_at или запрашивайте лида заново, чтобы получить его актуальное состояние.
  • Проверяйте каждую подпись и отклоняйте старые временные метки.
  • Новый секрет сразу заменяет старый. Выкатите его сразу после обновления, а доставки, не прошедшие за это время, будут повторены с подписью новым секретом.
  • События и доставки хранятся 30 дней.