Documentation / Complete
Webhooks with the API (and the one born with the onboarding)
POST /v1/webhooks
The same webhooks as the panel, managed with your API key at /v1/webhooks: create (POST /v1/webhooks), list (GET /v1/webhooks), get one (GET /v1/webhooks/:id), change it (PATCH /v1/webhooks/:id), delete it (DELETE /v1/webhooks/:id), test it (POST /v1/webhooks/:id/test, a signed event sent right away) and rotate its secret (POST /v1/webhooks/:id/rotate-secret). A webhook without connectionId is account-wide; with it, it receives only that number's events and replaces the account webhook for that number. The number is given as its conn_… or your externalId, and that is how it comes back in every response (with its externalId next to it) — never an internal id. The signing secret (whsec_…) and the header token, if you asked for one with headerToken: true, are shown once.
The webhook born with the onboarding. If you integrate with onboarding links, send webhook: { url, events } in POST /v1/onboarding-links: we validate it when the link is created and return its secret right then. When your customer finishes, the number is born with its webhook, signed with THAT secret, and before connection.created — that event already reaches it. Find it later with 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"Limit: 2 webhooks per number (one live and one to migrate without a gap), 2 × your numbers for all number webhooks together, and 5 account-wide; disabled ones count too. Going over it when creating or moving one to a number → 422 WEBHOOK_LIMIT_REACHED with details.scope, used and limit. Webhooks you already had above it keep working: you can edit, test, rotate and delete them.
List filters: ?connectionId=conn_… (or your externalId), ?connectionId=none (account-wide only) or ?externalId= (empty if that number does not exist yet). A wh_… or a number of another account gives the same 404 as one that does not exist.
Reconnecting the same number with a link carrying the same URL does not add another webhook: it updates that one, with the new link's secret. If the onboarding fails, no webhook is left behind.
Rotating the secret takes effect at once: from then on we sign with the new one and the old one stops verifying. Update it in your system right away.