SolidHooksSolidHooks
Começar
Casos de usoRotear para vários destinos

Roteie um webhook para todo serviço que precisa dele

O Stripe envia um evento de fatura uma vez. Seu serviço de cobrança precisa dele, seu data warehouse quer uma cópia e o agente que concilia os livros contábeis também quer uma. O SolidHooks aceita essa solicitação uma vez, armazena o corpo bruto e depois o distribui por quantas conexões você tiver definido. Cada uma mantém seu próprio filtro, seu próprio contador de tentativas e seu próprio comprovante.
Uma solicitação de entradaNovas tentativas independentes por destinoCobrança por entrega
O que este guia cobre
  1. 01Como origens, destinos e conexões se encaixam
  2. 02Filtrar quais eventos chegam a qual destino
  3. 03O que a distribuição faz com as novas tentativas, a vazão e a sua fatura
  4. 04Recuperar um destino sem afetar os outros
  5. 05Ler a fila enquanto ela escoa

Três objetos, e só três

Uma origem é um endpoint de entrada. Ela tem um maskId e uma ingestUrl, e essa URL é a única credencial de que o seu provedor precisa. Um destino é para onde você quer enviar eventos, com seus próprios cabeçalhos e seu próprio limite de taxa. Uma conexão liga uma origem a um destino e decide quais eventos fazem o trajeto.

A distribuição é o que você tem quando uma origem tem mais de uma conexão. Nada muda na origem, e o provedor nunca fica sabendo.

Provider
POST once
Source
verified, stored
Connections
3 matched
Deliveries
3 billed
O provedor recebe uma resposta. Tudo depois da origem acontece do nosso lado, então um destino lento não pode levar o seu provedor a tentar de novo.

Conecte um segundo destino

Supondo que uma origem já exista, adicionar outro consumidor é um destino e uma conexão. Nenhum dos dois mexe no provedor.

  1. 1

    Crie o destino

    POST /v1/destinations com a URL que você quer que seja chamada. Cabeçalhos personalizados são aceitos, e os que você marcar como sensíveis são criptografados em repouso e nunca retornados pela API.

  2. 2

    Conecte-o à origem

    Uma conexão define o par e, opcionalmente, restringe o que passa por ela. Omita filterRules.eventTypes para encaminhar tudo.

    curl
    curl -s "$API/v1/connections" \  -H "authorization: Bearer $SDHK_KEY" \  -H 'content-type: application/json' \  -d '{    "name": "Stripe → billing service",    "sourceId": "src_...",    "destinationId": "dest_...",    "filterRules": { "eventTypes": ["invoice.payment_succeeded"] }  }'
  3. 3

    Envie um evento de teste

    Envie para a URL de ingestão da origem. Uma origem com verificação de assinatura configurada precisa de uma assinatura válida e, sem ela, a solicitação é respondida com 401 e armazenada com status rejected. Quando um evento chega, você deve ver duas entregas associadas a ele, uma por conexão, cada uma com seu próprio status.

Filtrar sem uma linguagem de regras

Uma conexão faz a correspondência pelo tipo de evento e por nada mais. O tipo vem do eventTypeField da origem, um caminho em notação de ponto dentro da solicitação. Provedores conhecidos definem um padrão sensato, então uma origem GitHub lê headers.x-github-event e uma origem Stripe lê o tipo no corpo sem que você configure nada.

Essa única configuração cobre mais do que parece, porque é você quem escolhe o caminho. Aponte uma origem para body.action e as conexões dela filtram pela ação em vez do cabeçalho.

curl
# A GitHub source filters on the event header by default.# Point eventTypeField at a body path to filter on something else:curl -s -X PATCH "$API/v1/sources/src_..." \  -H "authorization: Bearer $SDHK_KEY" \  -H 'content-type: application/json' \  -d '{ "data": { "eventTypeField": "body.action" } }' # Connections on that source now match on the action, not the header:#   "filterRules": { "eventTypes": ["review_requested"] }
Vale saber
Um evento excluído por todos os filtros continua armazenado, com status filtered. Você não o perdeu. Adicione a conexão que faltava e reenvie.

Quanto custa distribuir

A unidade cobrada é a entrega, ou seja, um evento enviado a um destino conectado. Três conexões em um evento são três entregas. O preço acompanha o trabalho, não o número de assentos, então adicionar um quarto consumidor custa o mesmo, seja uma pessoa ou quarenta olhando o painel.

evento × conexãoUma entrega cobrada para cada
nova tentativa automáticaGrátis. Mesma entrega, outra tentativa.
nova tentativa manualGrátis. Nenhuma entrega nova é criada.
reenvio de eventoCobrado. Cria entregas novas em todas as conexões correspondentes.
solicitação duplicadaNada a cobrar. Uma solicitação deduplicada é respondida com 200 e nunca chega à distribuição.

Quando um destino cai

As entregas falham de forma independente. Cada uma é tentada de novo em uma curva de backoff que começa em 5 segundos e se abre até 10 horas entre tentativas: três tentativas com teto de uma hora no Hobby, oito com teto de 36 horas nos planos pagos, que a curva atinge depois de cerca de 28 horas. Depois disso, ela espera por você em vez de desaparecer. As outras conexões do mesmo evento não são afetadas e já terão sido concluídas.

Nem toda resposta fora de 2xx conta como falha, o que importa quando você está lendo um log de tentativas às 3h da manhã.

2xxSucesso.
3xxFalha. Redirecionamentos nunca são seguidos, então aponte o destino para a URL final.
410 GoneDois 410 seguidos desativam o destino e enviam um e-mail para os donos e admins. Qualquer outra resposta no meio zera a contagem, e editar o destino também, já que uma ocorrência pertence à configuração que a causou.
429, 502, 504Fica em espera em vez de falhar. Respeita Retry-After até uma hora e não custa nenhuma tentativa. São no máximo cinco esperas por entrega, e a espera é recusada antes disso se fosse ultrapassar o teto de novas tentativas do seu plano; em qualquer caso, a entrega segue para o caminho de falha.

Existem dois caminhos de recuperação, e eles não são intercambiáveis. POST /v1/deliveries/{id}/retry reenvia uma entrega para um destino e não cria nada novo, então é grátis. POST /v1/events/{id}/replay passa o evento pelas conexões dele de novo, o que produz entregas novas e é cobrado. Use a nova tentativa quando um único consumidor falhou, e o reenvio quando você adicionou uma conexão depois.

O reenvio recusa um evento rejeitado
Um evento rejected falhou na verificação de assinatura, então reenviá-lo empurraria conteúdo não verificado para os seus destinos em nome daquela origem. Isso retorna 400. Uma solicitação descartada como duplicata nem é armazenada, então também não há nada a reenviar.

Cadência, que não é falha

Há dois limites de taxa no lado de saída: as entregas por segundo do seu plano e qualquer limite que você defina em um destino específico. Ultrapassar qualquer um deles coloca a entrega na fila em vez de fazê-la falhar. O status dela aparece como throttled, com throttledReason igual a rate_limit, nenhuma tentativa é gasta e ela é enviada quando houver capacidade. O mesmo status com destination_busy é o outro tipo de pausa: o destino a pediu com um 429, 502 ou 504.

Isso importa mais enquanto um destino está se recuperando. Um serviço que acabou de voltar não recebe todo o acúmulo de uma vez, e ninguém é acionado por uma fila que está escoando corretamente. Uma entrega que já foi retida mantém um timestamp throttledAt mesmo depois de ser concluída, então a pausa fica visível depois.

curl
# Everything that was excluded by a filtercurl -s "$API/v1/events?status=filtered" -H "authorization: Bearer $SDHK_KEY" # What is waiting in the outbound queue right nowcurl -s "$API/v1/deliveries/stats" -H "authorization: Bearer $SDHK_KEY"→ { "pending": 4, "throttled": 412 }

Práticas que valem a pena

  • Dê a cada consumidor sua própria conexão em vez de compartilhar uma e ramificar dentro do seu handler. Conexões separadas dão estados de novas tentativas separados e comprovantes separados.
  • Defina um limite de taxa em qualquer destino que você sabe que é frágil. Para os dois lados, enfileirar sai mais barato que uma rajada de falhas seguida de backoff.
  • Filtre na conexão, não no seu handler. Um evento que o seu serviço ia descartar ainda custa uma entrega se você deixá-lo passar.
  • Consulte ?status=filtered quando um consumidor relatar eventos ausentes. A causa mais comum é um eventTypeField apontando para um lugar diferente do que você imaginava.
  • Torne os handlers idempotentes. O reenvio existe, as pessoas o usam durante incidentes, e o seu endpoint deve tratar uma segunda cópia como algo sem efeito.

Perguntas

Distribua uma origem para tudo o que precisa dela

Crie uma origem, adicione tantas conexões quanto forem os seus consumidores e acompanhe cada uma se estabilizar.