Registrar um endpoint
https://. As entregas carregam fatos sobre os seus próprios clientes e
uma assinatura que prova quem enviou, e nenhum dos dois sobrevive a ser lido no
fio. Um redirect é recusado em vez de seguido — registre o endereço que de fato
responde.
Um endpoint pertence a um ambiente e nunca sai dele: um endpoint de teste
ouve tráfego de teste, um de produção ouve tráfego de produção. Fatos sobre a
própria organização (organization.created, member.invited) não carregam
ambiente e vão para os endpoints live — uma organização tem uma existência
real só, e o ambiente de teste é onde o plano de clientes é ensaiado, não onde a
organização está.
subscribed_events vazio significa todos. Um tipo adicionado ao catálogo
depois passa a chegar sem você mexer em nada, o que costuma ser o que se quer no
primeiro endpoint e raramente é o que se quer no terceiro.
O payload
data carrega ids, não fotografias — releia o recurso para saber o estado
atual. Isso mantém nomes e endereços fora de toda fila, log e buffer de retry
entre nós e você, e significa que um handler atrasado lê o que é verdade agora em
vez do que era verdade quando o evento foi escrito.
A entrega é ao menos uma vez, e fora de ordem
Isso é o contrato, não uma ressalva. A mesma entrega pode chegar duas vezes e a ordem de chegada não é a ordem dos fatos. Dois campos existem para tornar isso tratável, e usá-los não é opcional:idé por onde você deduplica. Ele é estável em todo retry e em um reenvio manual — um reenvio é o mesmo fato chegando de novo, e um handler que já processou precisa poder dizer isso. Anote o id antes de agir sobre o evento; ignore o que já viu.sequenceé por onde você ordena. É um número global monotônico atribuído no commit: de dois eventos sobre o mesmo contato, o maior é o fato mais recente. Compare-o. Não compare horários de chegada, e não assuma quecontact.identifiedchega antes decontact.signed_in.
UserKit-Delivery-Id
nomeia este conjunto de tentativas. Um retry mantém o mesmo, um reenvio ganha
outro. É o que se cita numa conversa de suporte; UserKit-Event-Id é o que o seu
código usa para decidir.
Verificando a assinatura
Toda requisição carrega:v1 é HMAC-SHA256(segredo, "<t>.<corpo cru>"), em hex. Assine os bytes
crus que você recebeu — fazer parse e serializar de novo muda os bytes.
v1 como uma etiqueta em vez de uma
suposição: leia os pares que você conhece e ignore o resto, para que um algoritmo
futuro possa ser adicionado ao lado deste em vez de no lugar dele.
O segredo pode ser exibido de novo
Diferente de uma API key, você pode lê-lo outra vez emGET /v1/organization/webhooks/{id}/secret.
Isso é uma consequência, não uma conveniência. Uma API key só é comparada,
então guardar o hash basta; um segredo de webhook precisa assinar cada
entrega, então a linha guarda o segredo de qualquer jeito. Uma vez que isso é
verdade, mostrá-lo uma única vez só impediria você de ler o que o banco contém às
claras — ao custo da única recuperação que não é “rotacionar e quebrar todos os
verificadores em produção de uma vez”.
A rotação é o controle de verdade, e ela não tem janela de tolerância: o novo
segredo vale a partir da próxima entrega. Dois segredos vivos significariam um
verificador que aceita qualquer um dos dois, que é exatamente a propriedade que a
rotação existe para remover. Publique o segredo novo primeiro, rotacione depois.
Retentativas, e quando paramos
O seu endpoint tem dez segundos para responder. Qualquer2xx é sucesso;
confirme primeiro e faça o trabalho depois.
Uma tentativa que falha é retentada doze vezes, dobrando a partir de 30
segundos e com teto de seis horas:
Desligamento automático
Cinco entregas seguidas esgotando todas as tentativas desligam o endpoint. Isso leva pelo menos as quatorze horas que a primeira delas passou retentando, sobre cinco fatos diferentes, que é o que “sustentado” precisa significar antes de pararmos de chamar o seu servidor. Um único sucesso zera a sequência. Um endpoint desligado não recebe nada novo na fila — e os owners da sua organização recebem um e-mail, porque uma integração que fica quieta sem avisar é descoberta pela ausência. Nada se perde: as entregas estão no histórico, e reenviar uma é um clique assim que o endereço voltar. Religar zera a sequência, então a primeira falha depois do conserto não desliga tudo de novo.O histórico, o reenvio e o disparo de teste
GET /v1/organization/webhooks/{id}/deliveries são as últimas cem entregas, mais
recentes primeiro, com as tentativas gastas, o status que voltou e quanto tempo
levou. Entregas concluídas ficam 30 dias; uma pendente nunca é removida.
POST …/deliveries/{deliveryId}/replay envia o corpo guardado de novo, byte a
byte, com um novo id de entrega e o mesmo id de evento.
POST …/test enfileira uma entrega com o tipo webhook.test — deliberadamente
ausente do catálogo, para que um handler que reage a ele esteja reagindo a um
botão que alguém apertou, não a um fato.
Os dois respondem 202: a linha é durável, a tentativa ainda não aconteceu, e o
resultado aparece no histórico.
O catálogo publicado
Este é o contrato contra o qual você constrói, e é a mesma lista que o fan-out da entrega lê — um tipo ausente daqui é um tipo que não vai chegar.GET /v1/organization/webhooks/events devolve o catálogo em tempo de execução.
Um fato é publicado internamente e nunca entregue:
webhook.endpoint_disabled. Ele seria endereçado ao endpoint que acabou de parar
de responder, o que é uma mensagem para ninguém — o e-mail para os owners é o que
carrega esse recado.
Editar ou apagar um post de feedback também não é entregue, e a razão vale ser
dita porque não é sigilo: um post renomeado é público no instante em que é
salvo. Nada reage a “um typo foi corrigido”, e entregabilidade é uma porta de
mão única — ligar depois é uma capacidade nova, desligar é um contrato quebrado.
Quando um fato é publicável e ninguém tem uso para ele, segurar é a única
direção que continua reversível.
O plano de auditoria
Todo endpoint acima está no planoproduct: os fatos do seu produto, filtrados
por subscribed_events. Existe um segundo plano, e cadastrar um endpoint com
"plane": "audit" o coloca lá.
Um endpoint de auditoria recebe a trilha de auditoria do staff — cada ação
que alguém executou no painel, os mesmos registros que
GET /v1/organization/audit-events mostra. Nenhum desses tipos está no catálogo
acima e nenhum deles chega a um endpoint de produto: ler a trilha é audit:read,
que só owner tem, enquanto webhooks:manage também está com admin — entregar
esses fatos pelo fan-out comum tiraria um registro de acesso exclusivo do owner
da organização sem que ninguém encostasse na permissão mais estreita. O plano de
auditoria é a porta que tem essa permissão.
Quatro coisas o diferenciam de um endpoint de produto, e todas seguem do fato de
um registro ser um registro:
- Ele leva o ator. Cada entrega tem um objeto
actora mais —user_id,email,ip,user_agent— que nenhuma entrega de produto tem, seja qual for o fato. - Ele recebe tudo.
subscribed_eventsé recusado: um registro com as linhas interessantes filtradas se lê como se nada tivesse acontecido. - Ele é da organização, então é cadastrado no ambiente
livee recebe as ações de staff dos dois ambientes. A trilha não é de um ambiente. - Ele exige
audit:deliver— só do owner, e deliberadamente separada tanto dewebhooks:managequanto deaudit:read: ler um registro e decidir para onde ele vai para sempre são atos diferentes. Cadastrar um também exige um plano que carregue a capacidade, e um plano que deixa de carregá-la interrompe as entregas. A trilha continua sendo gravada e guardada em todos os planos; o que um plano compra é o transporte.
O plano do cliente: webhooks que você revende
Os dois planos acima entregam para você. O terceiro entrega para os seus clientes: um time seu cadastra um endpoint no portal e passa a receber os eventos que você registrou sobre aquele time — com os nomes do seu produto, não com os do catálogo acima. É a resposta para o pedido que todo SaaS B2B recebe cedo: “me avisa quando um pedido for aprovado, em vez de eu ficar consultando sua API”. Sem isso, você construiria fila, assinatura, retentativa, log e tela do zero. Com isso, seus clientes usam a mesma infraestrutura que você já usa.Como funciona, de ponta a ponta
1. O seu backend reporta o fato, pela porta que já existe — nomeando o time a que ele pertence:/webhooks — ou
pela API, com a sessão de contato dele:
O que é diferente, e por quê
- O vocabulário é o seu. O
typeépedido.aprovado— o nome que você registrou. O catálogo do UserKit não chega aqui, e quem recebe nunca precisa saber que existe um UserKit no meio. subscribed_eventsé obrigatório. No planoproduct, vazio significa todos; aqui não pode ser vazio. O seu vocabulário é aberto — não há lista fechada contra a qual verificar — então “todos” significaria assinar todo nome que você venha a inventar, para um receptor que não escreveu nenhum deles. São de 1 a 20 nomes, e no máximo 5 endpoints por time.- O escopo é o time, e um fato sem time não vai para ninguém. A entrega casa
o
customer_iddo evento com o time dono do endpoint: um cliente nunca ouve o tráfego de outro. Uma linha de/v1/tracksemcustomer_idé um fato da pessoa, não de um time — e não é entregue a nenhum endpoint deste plano. Se o evento importa para o cliente, nomeie o time. - Os seus próprios endpoints não recebem esse eco. O evento que você acabou
de nos mandar não volta pelo plano
product. Um eco é a primeira metade de um laço, e a sua stream de analytics já tem esse dado. - O aviso de falha vai para o cliente, não para você. Se o servidor dele parar de responder e o endpoint for desligado, quem recebe o e-mail é quem pode consertar — os administradores daquele time, na língua deles, assinado com a sua marca. Você não vira suporte de terceiro nível de máquinas que não são suas.
- É do seu plano. Cadastrar exige um plano que carregue a capacidade
(
403 webhooks_not_availablesem ele), e um downgrade interrompe as entregas sem apagar nada — os endpoints continuam cadastrados para quando o plano voltar. É a mesma posição do plano de auditoria, e pelo mesmo motivo: o que se vende aqui não é o acesso do cliente ao dado dele, é o transporte que executamos continuamente em nome dele.
https:// obrigatório, HMAC sobre
t.corpo, doze tentativas, desligamento após cinco entregas esgotadas, segredo
reexibível e rotacionável, log de entregas e disparo de teste — tudo em
/v1/contact/customer/webhooks/{id}/….
Quem administra, do lado do cliente
A capacidade é$webhooks.manage, do vocabulário de papéis do seu plano de
clientes. O papel owner já vem com ela; se você definiu papéis próprios
(um “financeiro”, um “técnico”), decida qual deles deve poder apontar os eventos
do time para um servidor — é uma decisão sua, na tela de papéis, e não uma que
tomamos por você.
Permissão
Tudo acima exigewebhooks:manage, concedida a owner e admin por padrão. Não
há divisão entre leitura e escrita, porque a tela mostra o segredo de assinatura:
ler um endpoint é possuir a credencial dele.
O plano de auditoria acrescenta audit:deliver, só para owner — cadastrar,
reapontar, desligar ou apagar um endpoint de auditoria. Ler um deles continua em
webhooks:manage: perceber que existe uma exportação não é o mesmo ato que
escolher para onde ela vai.
O plano do cliente não usa nenhuma das duas. Ele é administrado pelo seu
cliente, na sessão de contato dele, sob $webhooks.manage — uma capacidade do
vocabulário de papéis do seu plano de clientes, não uma permissão de staff. A
consequência prática: os endpoints dos seus clientes não aparecem na sua
tela de webhooks e não são endereçáveis por /v1/organization/webhooks/{id}
(responde 404). Eles são credenciais de outra parte, e o segredo de assinatura
que a tela reexibe é dela, não sua.