Skip to content
WhatIsUp.dev

Endpoints de webhook

Diga ao WhatIsUp.dev para onde entregar as notificações de evento. Um cliente pode ter múltiplos endpoints (URLs diferentes para subconjuntos diferentes de eventos). Um canal pode ser a origem de eventos para múltiplos endpoints, e um endpoint pode assinar múltiplos canais — é um relacionamento muitos-para-muitos.

Modelo conceitual

Endpoints descrevem uma assinatura: para onde enviar, quais eventos enviar, e o secret de assinatura usado no HMAC. Os registros de entrega de fato estão em Entregas de webhook.

api_key → customer ──┬── channel ◄───┐
                     │                 │
                     └── webhook_endpoint
                              │
                              └── webhook_delivery (per event)

Listar endpoints

GET/v1/webhook-endpointsBearer · API key

Retorna todos os endpoints do cliente autenticado.

Criar um endpoint

POST/v1/webhook-endpointsBearer · API key
Schema · Request body
FieldTypeRequiredNotes
urlstring · URLrequired
eventsarray<`message.received` \| `message.sent` \| `message.reaction` \| `message.status` \| `channel.connected` \| `channel.disconnected` \| `channel.status` \| `qr.updated` \| `group.created` \| `group.updated` \| `group.participant_added` \| `group.participant_removed` \| `group.admin_promoted` \| `group.admin_demoted` \| `chat.updated` \| `chat.archived` \| `chat.pinned` \| `presence.updated` \| `order.placed` \| `order.cancelled` \| `cart.updated` \| `story.viewed` \| `call.offered` \| `call.terminated` \| `contact.resolved`>required
channel_idstring · uuidoptional
signing_secretstringoptional
enabledbooleanoptional
chat_type_filter`all` \| `direct_only` \| `groups_only`optional
curl -sX POST "$WHATISUP_API/v1/webhook-endpoints" \
  -H "Authorization: Bearer $WHATISUP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://api.acme.dev/whatisup/webhook","events":["message.received","message.sent"],"channel_id":"8d653c66-e4ff-43ee-97da-3de5ad5680d4"}'

A resposta inclui o secret de assinatura em texto puro — somente nesta chamada. Guarde-o; nunca mais mostramos. (Podemos rotacioná-lo; não conseguimos recuperá-lo.)

Schema · Response · WebhookEndpoint
FieldTypeRequiredNotes
idstring · uuidrequired
customer_idstring · uuidrequired
channel_idstring · uuidrequired · nullable
urlstring · URLrequired
eventsarray<`message.received` \| `message.sent` \| `message.reaction` \| `message.status` \| `channel.connected` \| `channel.disconnected` \| `channel.status` \| `qr.updated` \| `group.created` \| `group.updated` \| `group.participant_added` \| `group.participant_removed` \| `group.admin_promoted` \| `group.admin_demoted` \| `chat.updated` \| `chat.archived` \| `chat.pinned` \| `presence.updated` \| `order.placed` \| `order.cancelled` \| `cart.updated` \| `story.viewed` \| `call.offered` \| `call.terminated` \| `contact.resolved`>required
enabledbooleanrequired
has_custom_signing_secretbooleanrequired
chat_type_filter`all` \| `direct_only` \| `groups_only`required
created_atstring · ISO 8601required
updated_atstring · ISO 8601required

A url é validada contra SSRF no momento da criação e no momento da entrega (defesa contra DNS-rebind). IPs de loopback / link-local / RFC1918 / cloud-metadata são rejeitados com um 400. Em produção (NODE_ENV=production), apenas https:// é aceito.

Atualizar um endpoint

PATCH/v1/webhook-endpoints/:idBearer · API key
Schema · Request body
FieldTypeRequiredNotes
urlstring · URLoptional
eventsarray<`message.received` \| `message.sent` \| `message.reaction` \| `message.status` \| `channel.connected` \| `channel.disconnected` \| `channel.status` \| `qr.updated` \| `group.created` \| `group.updated` \| `group.participant_added` \| `group.participant_removed` \| `group.admin_promoted` \| `group.admin_demoted` \| `chat.updated` \| `chat.archived` \| `chat.pinned` \| `presence.updated` \| `order.placed` \| `order.cancelled` \| `cart.updated` \| `story.viewed` \| `call.offered` \| `call.terminated` \| `contact.resolved`>optional
signing_secretstring \| unknownoptional
enabledbooleanoptional
chat_type_filter`all` \| `direct_only` \| `groups_only`optional

Não existe flag rotate_secret. Para rotacionar, faça PATCH no endpoint com um novo signing_secret (string de 16–255 caracteres, valor seu):

{ "signing_secret": "whsec_um-novo-secret-de-32-ou-mais-chars" }

Passe "signing_secret": null para descartar seu secret customizado e voltar ao padrão do gateway. Qualquer das duas mudanças vale a partir da próxima entrega — o secret antigo para de funcionar na hora, então atualize seu receptor primeiro.

Testar um endpoint

Duas rotas disparam uma entrega sintética assinada. As duas devolvem o mesmo superconjunto, então um parser só lê as duas:

  • POST /v1/webhook-endpoints/test — testa qualquer URL, guardada ou não. Corpo: { "url": "..." }.
  • POST /v1/webhook-endpoints/:id/test — testa um endpoint guardado com o secret real dele.
{
  "ok": true,
  "status": "success",
  "outcome": "success",
  "http_status": 200,
  "latency_ms": 372,
  "elapsed_ms": 372,
  "signed_with": "endpoint",
  "endpoint_id": "0f2c5ad1-8b47-4e93-a5d2-6c81e04fb7a9"
}

status significa coisas diferentes nas duas rotas, mantido assim por compatibilidade: em /test é o código HTTP do upstream, em /:id/test é o discriminador de sucesso/falha. Leia ok — booleano com o mesmo significado nas duas. outcome é o discriminador com um nome que não colide.

signed_with: "gateway_default" significa que não temos secret guardado para aquela URL, então um 401 de volta quer dizer "seu receptor espera outro secret", não "seu receptor está quebrado".

Excluir um endpoint

DELETE/v1/webhook-endpoints/:idBearer · API key

Retorna 204 No Content. Entregas em andamento que já foram enfileiradas vão falhar contra o endpoint agora excluído e esgotar o orçamento de retentativas normalmente.