SolidHooksSolidHooks
Empezar
Casos de usoEnrutar a varios destinos

Envía un webhook a todos los servicios que lo necesitan

Stripe envía un evento de factura una vez. Tu servicio de facturación lo necesita, tu almacén de datos quiere una copia y el agente que concilia la contabilidad también quiere una. SolidHooks acepta esa solicitud una vez, guarda el cuerpo sin procesar y la distribuye por todas las conexiones que hayas definido. Cada una mantiene su propio filtro, su propio contador de intentos y su propio comprobante.
Una solicitud entranteReintentos independientes por destinoFacturado por entrega
Qué cubre esta guía
  1. 01Cómo encajan orígenes, destinos y conexiones
  2. 02Filtrar qué eventos llegan a qué destino
  3. 03Qué supone la distribución para los reintentos, el rendimiento y tu factura
  4. 04Recuperar un destino sin afectar a los demás
  5. 05Interpretar la cola mientras se vacía

Tres objetos, y solo tres

Un origen es un endpoint de entrada. Tiene un maskId y una ingestUrl, y esa URL es la única credencial que necesita tu proveedor. Un destino es un lugar al que quieres enviar eventos, con sus propias cabeceras y su propio límite de tasa. Una conexión une un origen con un destino y decide qué eventos hacen el viaje.

La distribución es lo que obtienes cuando un origen tiene más de una conexión. Al origen no le cambia nada y el proveedor nunca se entera.

Provider
POST once
Source
verified, stored
Connections
3 matched
Deliveries
3 billed
El proveedor recibe una sola respuesta. Todo lo que pasa después del origen ocurre de nuestro lado, así que un destino lento no puede hacer que tu proveedor reintente.

Conecta un segundo destino

Si ya existe un origen, añadir otro consumidor es un destino y una conexión. Ninguno de los dos toca al proveedor.

  1. 1

    Crea el destino

    POST /v1/destinations con la URL a la que quieres llamar. Se admiten cabeceras personalizadas, y las que marques como sensibles se cifran en reposo y la API nunca las devuelve.

  2. 2

    Conéctalo al origen

    Una conexión indica la pareja y, opcionalmente, restringe lo que pasa por ella. Omite filterRules.eventTypes para reenviarlo todo.

    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

    Envía un evento de prueba

    Haz un POST a la URL de ingesta del origen. Un origen con verificación de firma configurada necesita una firma válida; sin ella, la solicitud recibe un 401 y se guarda con el estado rejected. Cuando llegue un evento, deberías ver dos entregas asociadas, una por conexión, cada una con su propio estado.

Filtrar sin un lenguaje de reglas

Una conexión compara el tipo de evento y nada más. El tipo sale del eventTypeField del origen, una ruta con puntos dentro de la solicitud. Los proveedores conocidos fijan un valor por defecto razonable, así que un origen de GitHub lee headers.x-github-event y un origen de Stripe lee el tipo del cuerpo sin que configures nada.

Ese único ajuste cubre más de lo que parece, porque tú eliges la ruta. Apunta un origen a body.action y sus conexiones filtrarán por la acción en lugar de por la cabecera.

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"] }
Conviene saberlo
Un evento excluido por todos los filtros se guarda igualmente, con el estado filtered. No lo has perdido. Añade la conexión que faltaba y repítelo.

Lo que cuesta distribuir

La unidad facturable es la entrega, es decir, un evento enviado a un destino conectado. Tres conexiones en un evento son tres entregas. El precio sigue al trabajo y no al número de plazas, así que añadir un cuarto consumidor cuesta lo mismo tanto si mira el panel una persona como si lo miran cuarenta.

evento × conexiónUna entrega facturable por cada una
reintento automáticoGratis. La misma entrega, otro intento.
reintento manualGratis. No se crea ninguna entrega nueva.
repetición de eventoSe factura. Crea entregas nuevas en cada conexión coincidente.
solicitud duplicadaNo hay nada que facturar. Una solicitud deduplicada recibe un 200 y nunca llega a la distribución.

Cuando un destino se cae

Las entregas fallan de forma independiente. Cada una se reintenta con una espera exponencial que empieza en 5 segundos y se amplía hasta 10 horas entre intentos: tres intentos con un techo de una hora en Hobby y ocho con un techo de 36 horas en los planes de pago, que la curva alcanza tras unas 28 horas. Después, la entrega te espera en lugar de desaparecer. Las demás conexiones del mismo evento no se ven afectadas y ya habrán tenido éxito.

No toda respuesta que no sea 2xx cuenta como fallo, y eso importa cuando lees un registro de intentos a las 3 de la madrugada.

2xxÉxito.
3xxFallo. Las redirecciones nunca se siguen, así que apunta el destino a su URL final.
410 GoneDos 410 seguidos desactivan el destino y envían un correo a los propietarios y administradores. Cualquier otra respuesta entre medias reinicia el recuento, y editar el destino también, porque cada aviso pertenece a la configuración que lo provocó.
429, 502, 504En espera en lugar de fallida. Respeta Retry-After hasta una hora y no gasta ningún intento. Como máximo cinco esperas por entrega, y se rechaza antes si la espera fuera a superar el techo de reintentos de tu plan; en ambos casos pasa a la ruta de fallo.

Hay dos formas de recuperarse y no son intercambiables. POST /v1/deliveries/{id}/retry reenvía una entrega a un destino y no crea nada nuevo, así que es gratis. POST /v1/events/{id}/replay vuelve a pasar el evento por sus conexiones, lo que genera entregas nuevas y se factura. Usa el reintento cuando falló un solo consumidor, y la repetición cuando añadiste una conexión a posteriori.

La repetición rechaza un evento rechazado
Un evento rejected no superó la verificación de firma, así que repetirlo enviaría contenido no verificado a tus destinos en nombre de ese origen. Devuelve 400. Una solicitud descartada como duplicada no se guarda, así que tampoco hay nada que repetir.

Dosificar no es fallar

Hay dos límites de tasa en la salida: las entregas por segundo de tu plan y el límite que fijes en cada destino. Superar cualquiera de los dos pone la entrega en cola en lugar de hacerla fallar. Su estado pasa a throttled con un throttledReason de rate_limit, no se gasta ningún intento y se envía cuando hay capacidad. El mismo estado con destination_busy es el otro tipo de pausa: la pidió el destino con un 429, 502 o 504.

Esto importa sobre todo mientras un destino se recupera. Un servicio que acaba de volver no recibe toda la acumulación de golpe, y no te avisan de guardia por una cola que se está vaciando correctamente. Una entrega que estuvo retenida alguna vez conserva una marca de tiempo throttledAt incluso después de tener éxito, así que la pausa se ve después.

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ácticas que conviene adoptar

  • Dale a cada consumidor su propia conexión en lugar de compartir una y ramificar dentro de tu handler. Las conexiones separadas te dan estados de reintento separados y comprobantes separados.
  • Fija un límite de tasa en cualquier destino que sepas que es frágil. Poner en cola sale más barato a ambas partes que una ráfaga de fallos seguida de esperas.
  • Filtra en la conexión, no en tu handler. Un evento que tu servicio iba a descartar sigue costando una entrega si lo dejas pasar.
  • Consulta ?status=filtered cuando un consumidor diga que le faltan eventos. La causa habitual es un eventTypeField que apunta a otro sitio del que creías.
  • Haz que tus handlers sean idempotentes. La repetición existe, la gente la usa durante los incidentes, y tu endpoint debería tratar una segunda copia como algo que no hace nada.

Preguntas

Distribuye un origen a todo lo que lo necesita

Crea un origen, añade tantas conexiones como consumidores tengas y observa cómo se asienta cada una.