Los mismos webhooks del panel, manejados con tu clave de API en /v1/webhooks: crear (POST /v1/webhooks), listar (GET /v1/webhooks), ver uno (GET /v1/webhooks/:id), cambiarlo (PATCH /v1/webhooks/:id), borrarlo (DELETE /v1/webhooks/:id), probarlo (POST /v1/webhooks/:id/test, un evento firmado que sale en el acto) y rotar su secreto (POST /v1/webhooks/:id/rotate-secret). Un webhook sin connectionId es de la cuenta; con él, recibe sólo los eventos de ese número y reemplaza al de la cuenta para ese número. El número se indica con su conn_… o con tu externalId, y así sale también en cada respuesta (con su externalId al lado): nunca un id interno. El secreto de firma (whsec_…) y el token de cabecera, si lo pediste con headerToken: true, salen una sola vez.
El webhook que nace con el alta. Si integrás con enlaces de alta, mandá webhook: { url, events } en POST /v1/onboarding-links: lo validamos al crear el enlace y el secreto te lo devolvemos en ese momento. Cuando tu cliente termina, el número nace con su webhook, firmado con ESE secreto, y antes de connection.created: ese evento ya te llega ahí. Después lo encontrás con GET /v1/webhooks?externalId=….
# 1) onboarding link + the webhook of the number it will connect
curl -X POST https://api.waiaconnect.com/v1/onboarding-links \
-H "Authorization: Bearer wc_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"externalId": "pharmacy-42", "webhook": {"url": "https://api.yourcompany.com/hooks/connect", "events": ["connection.created", "message.received", "message.status"]}}'
# → { "url": "https://…/c/<token>", "publicId": "onb_…",
# "webhook": { "secret": "whsec_…", "createdOnCompletion": true, … } } ← store the secret NOW
# 2) after your customer connects: the number's webhooks, by your externalId
curl "https://api.waiaconnect.com/v1/webhooks?externalId=pharmacy-42" -H "Authorization: Bearer wc_live_YOUR_API_KEY"
# → { "webhooks": [ { "id": "wh_…", "connectionId": "conn_…", "externalId": "pharmacy-42", … } ] }
# 3) create one directly (connectionId = conn_… or your externalId; omit = account-wide)
curl -X POST https://api.waiaconnect.com/v1/webhooks \
-H "Authorization: Bearer wc_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://api.yourcompany.com/hooks/connect-2", "connectionId": "pharmacy-42"}'
# rotate the secret (the old one stops verifying at once)
curl -X POST https://api.waiaconnect.com/v1/webhooks/wh_…/rotate-secret -H "Authorization: Bearer wc_live_YOUR_API_KEY"Tope: 2 webhooks por número (uno vivo y otro para migrar sin cortar), 2 × tus números para todos los de número juntos, y 5 de cuenta; cuentan los apagados también. Pasarlo al crear o al mover uno de número → 422 WEBHOOK_LIMIT_REACHED con details.scope, used y limit. Los que ya tenías por encima siguen andando: los podés editar, probar, rotar y borrar.
Filtros de la lista: ?connectionId=conn_… (o tu externalId), ?connectionId=none (sólo los de cuenta) o ?externalId= (vacía si ese número todavía no existe). Un wh_… o un número de otra cuenta da el mismo 404 que uno que no existe.
Reconectar el mismo número con un enlace que trae la misma URL no suma otro webhook: actualiza ése, con el secreto del enlace nuevo. Si el alta falla, no queda ningún webhook.
Rotar el secreto vale en el acto: desde ese momento firmamos con el nuevo y el viejo deja de verificar. Actualizalo en tu sistema enseguida.