Integrações
Webhooks
Seja avisado em segundos quando alguém se inscreve, paga, entra ou elimina os seus dados, num corpo assinado à maneira Standard Webhooks para saber que fomos nós.
Como funcionam
Um ponto de acesso de webhook é um endereço https seu que a AttendBeacon chama com um POST quando algo acontece nos seus eventos. Adicione um na área de trabalho em Integrações › Webhooks, ou através da API (POST /webhooks), escolha os tipos de evento que recebe e guarde o segredo de assinatura, que é mostrado uma única vez.
A AttendBeacon escreve primeiro cada evento numa fila na sua base de dados e só depois chama cada ponto de acesso que o escuta, por isso um reinício do nosso lado nunca perde nenhum. Cada chamada leva o mesmo id de mensagem em todas as tentativas, para que uma repetição possa ser reconhecida.
Os tipos de evento
| Tipo | Enviado quando | Para subscrever precisa de |
|---|---|---|
attendee.registered | A registration was created. | attendees:read people:read |
attendee.updated | A registration was changed. | attendees:read people:read |
attendee.cancelled | A registration was cancelled. | attendees:read people:read |
attendee.erased | An attendee deleted their data. | attendees:read |
order.paid | An order was paid. | orders:read people:read |
checkin.created | Somebody was let in. | checkins:read people:read |
feedback.given | A rating was given. | surveys:read people:read |
lead.captured | A lead was captured. | sponsors:read people:read |
certificate.issued | A certificate was issued. | attendees:read |
content.published | Session slides were published. | program:read |
member.joined | Somebody joined a community. | communities:read |
member.left | A member left a community. | communities:read |
stay.requested | A hotel stay was asked for. | events:read |
stay.assigned | A hotel stay was assigned. | events:read |
stay.changed | A hotel stay changed. | events:read |
stay.cancelled | A hotel stay was cancelled. | events:read |
shift.signed_up | A volunteer took a shift. | crew:read |
shift.dropped | A volunteer came off a shift. | crew:read |
travel.changed | A guest's travel changed. | events:read |
claim.submitted | An expense claim was sent in. | finance:read |
claim.approved | An expense claim was approved. | finance:read |
claim.paid | An expense claim was paid. | finance:read |
Eventos de webhook na referência mostra os campos de cada tipo e um exemplo. GET /webhooks/event-types responde com a mesma lista e o JSON schema de cada payload, para código que queira verificar o que recebe.
O que chega
POST /attendbeacon HTTP/1.1
Host: hooks.example.com
Content-Type: application/json
webhook-id: msg_2f6c1a9e4b7d4c0f8e3a5b9d1c7e0f42
webhook-timestamp: 1792130400
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=
{
"type": "attendee.registered",
"timestamp": "2026-10-14T12:00:00.000Z",
"data": {
"event": { "eventId": "b3ac0000-0000-4000-8000-000000000001", "slug": "beacon-summit", "name": "Beacon Summit" },
"attendee": {
"attendeeId": "b3ac0008-0000-4000-8000-000000000000",
"ticketTypeName": "Conference pass",
"firstName": "Emma",
"lastName": "Bakker",
"email": "emma.bakker0@example.com",
"status": "active",
"valid": true
}
}
}O corpo é { type, timestamp, data }. Um participante em data tem os mesmos campos do participante que a API responde, por isso o código escrito para um funciona para o outro. Os tipos sobre estadias em hotel, viagens, pedidos de reembolso, turnos, certificados, conteúdos e membros de comunidades trazem apenas ids, estados, datas e montantes, nunca um nome.
Responda com qualquer 2xx em 15 segundos. Faça o trabalho depois: coloque a mensagem numa fila sua e responda de imediato.
Verificar a assinatura
Qualquer pessoa pode enviar um POST para o seu endereço, por isso verifique cada chamada antes de confiar nela. A assinatura é o HMAC-SHA256 em Base64 de {webhook-id}.{webhook-timestamp}.{body} com o segredo do seu ponto de acesso (a parte depois de whsec_, descodificada de Base64), escrito como v1, seguido da assinatura. Enquanto um segredo está a ser substituído, o cabeçalho pode conter duas, separadas por um espaço; aceite a chamada quando uma corresponder.
- Verifique o corpo em bruto, exatamente como chegou, antes de qualquer análise de JSON.
- Recuse um timestamp a mais de cinco minutos do seu relógio, para que uma chamada gravada não possa ser repetida mais tarde.
- Compare em tempo constante.
- Memorize os ids de mensagem que já tratou e ignore as repetições.
import { createHmac, timingSafeEqual } from "node:crypto";
// secret: "whsec_..." as the workspace showed it; body: the raw request body as a string.
export function verify(secret, headers, body) {
const id = headers["webhook-id"];
const timestamp = Number(headers["webhook-timestamp"]);
if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false;
const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
const expected = createHmac("sha256", key).update(`${id}.${timestamp}.${body}`).digest("base64");
return headers["webhook-signature"].split(" ").some((candidate) => {
const [version, signature] = candidate.split(",");
return version === "v1" && signature.length === expected.length
&& timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
});
}using System.Security.Cryptography;
using System.Text;
// secret: "whsec_..." as the workspace showed it; body: the raw request body as a string.
static bool Verify(string secret, string id, string timestamp, string signatures, string body)
{
if (!long.TryParse(timestamp, out var seconds) || Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - seconds) > 300) return false;
var key = Convert.FromBase64String(secret.StartsWith("whsec_") ? secret[6..] : secret);
var expected = Encoding.ASCII.GetBytes("v1," + Convert.ToBase64String(HMACSHA256.HashData(key, Encoding.UTF8.GetBytes($"{id}.{timestamp}.{body}"))));
return signatures.Split(' ', StringSplitOptions.RemoveEmptyEntries)
.Any(candidate => CryptographicOperations.FixedTimeEquals(Encoding.ASCII.GetBytes(candidate), expected));
}import base64, hashlib, hmac, time
# secret: "whsec_..." as the workspace showed it; body: the raw request body as bytes.
def verify(secret: str, headers: dict, body: bytes) -> bool:
msg_id = headers["webhook-id"]
timestamp = headers["webhook-timestamp"]
if abs(time.time() - int(timestamp)) > 300:
return False
key = base64.b64decode(secret.removeprefix("whsec_"))
signed = f"{msg_id}.{timestamp}.".encode() + body
expected = "v1," + base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest()).decode()
return any(hmac.compare_digest(candidate, expected) for candidate in headers["webhook-signature"].split(" "))# $secret: "whsec_..." as the workspace showed it; $body: the raw request body as a string.
function Test-AttendBeaconSignature($secret, $id, $timestamp, $signatures, $body) {
$now = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds()
if ([math]::Abs($now - [long]$timestamp) -gt 300) { return $false }
$hmac = [System.Security.Cryptography.HMACSHA256]::new([Convert]::FromBase64String($secret -replace '^whsec_', ''))
$hash = $hmac.ComputeHash([Text.Encoding]::UTF8.GetBytes("$id.$timestamp.$body"))
$expected = [Text.Encoding]::ASCII.GetBytes("v1," + [Convert]::ToBase64String($hash))
foreach ($candidate in $signatures -split ' ') {
if ([System.Security.Cryptography.CryptographicOperations]::FixedTimeEquals([Text.Encoding]::ASCII.GetBytes($candidate), $expected)) { return $true }
}
return $false
}O esquema é o Standard Webhooks, por isso as bibliotecas dele para a sua linguagem verificam as nossas chamadas tal como estão, com o mesmo segredo.
Novas tentativas
Uma chamada que não responda 2xx em 15 segundos é tentada de novo, oito vezes ao longo de pouco menos de um dia: de imediato, após 10 segundos, um minuto, 5 minutos, 30 minutos, 2 horas, 6 horas e 15 horas. Todas as respostas que não sejam 2xx são repetidas, incluindo um 4xx: um ponto de acesso que responde 401 após uma implementação falhada normalmente está corrigido pela manhã.
A exceção é 410 Gone: desliga o ponto de acesso, porque o recetor disse que ele já não existe. Volte a ligá-lo na área de trabalho quando existir.
A página de cada ponto de acesso na área de trabalho lista as suas entregas durante 30 dias: o que foi enviado, cada tentativa e o que o ponto de acesso respondeu, com Reenviar para uma que falhou. Enviar evento de teste envia um webhook.test assinado enquanto acompanha.
Pontos de acesso através da API
Com webhooks:write, uma chave pode adicionar, alterar, testar e remover pontos de acesso por si, o que convém a software que configura os seus próprios: POST /webhooks responde com o ponto de acesso e o seu segredo, uma única vez; POST /webhooks/{webhookId}/secret cria um novo e desativa o antigo de imediato. Uma área de trabalho tem no máximo 20 pontos de acesso.
Plataformas de automatização
O Power Automate, o Zapier e o Make subscrevem através de POST /hooks, uma subscrição por fluxo, e recebem os mesmos eventos como JSON simples com data elevado ao nível superior: { id, type, timestamp, event, attendee }. Não precisam de segredo: o endereço que indicam é o seu próprio segredo. Consulte Power Automate e Zapier.