API: Webhook events
Webhook subscriptions push events from Nybero to your systems the moment they happen — the mirror image of inbound webhooks. This is what powers instant triggers in tools like Zapier and Make, and what you use to keep your own backend in sync without polling.
See the API overview for authentication and errors.
Event types
| Event | Fires when | data payload |
|---|---|---|
contact.created | A new contact appears (any source: inbound message, opt-in page, webhook, API) | { contact } |
contact.opted_out | A contact opts out (STOP & friends) | { contact } |
message.received | An inbound WhatsApp message arrives | { contact, message: { wa_message_id, type, text, is_new_contact } } |
form.submitted | A WhatsApp form (flow) is completed | { contact, flow_id, answers } |
tag.added | A tag is newly attached to a contact | { contact, tag } |
contact is the same object the Contacts API returns. GET /v1/events lists the available types programmatically.
Subscribe — POST /v1/subscriptions
curl -s https://app.nybero.com/api/v1/subscriptions \ -H "Authorization: Bearer $NYBERO_KEY" \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com/nybero-hook", "events": ["contact.created", "form.submitted"]}'{ "id": "7f3a…", "url": "https://example.com/nybero-hook", "events": ["contact.created", "form.submitted"], "secret": "9c41…", "active": true, "created_at": "2026-07-09T17:02:11Z"}The url must be a public https endpoint. Up to 20 active subscriptions per workspace. Store the secret — you’ll use it to verify deliveries.
Unsubscribe with DELETE /v1/subscriptions/{id} (idempotent, returns 204). GET /v1/subscriptions lists what’s active.
What a delivery looks like
Nybero POSTs JSON to your URL, usually within a few seconds of the event:
{ "id": "e0b2…", "event": "contact.created", "created_at": "2026-07-09T17:05:40Z", "data": { "contact": { "id": "0d9f…", "phone": "+4915112345678", "name": "Ada Example", "opt_in_status": "confirmed", "source": "landingpage", "attributes": {}, "created_at": "2026-07-09T17:05:39Z" } }}Headers on every delivery:
| Header | Meaning |
|---|---|
X-Nybero-Event | The event type |
X-Nybero-Delivery | Unique delivery id (use it for idempotency) |
X-Nybero-Signature | sha256=<HMAC-SHA256 of the raw body, keyed with your secret> |
Respond with any 2xx within 10 seconds. Anything else counts as a failure and is retried with exponential backoff (about 1, 2, 4, 8 and 16 minutes); after five failed attempts the delivery is dropped. Because of retries, the same delivery id can reach you more than once — deduplicate on X-Nybero-Delivery if your handler isn’t naturally idempotent.
Verifying the signature
Always verify before trusting a payload:
import hashlib, hmac
def verify(secret: str, raw_body: bytes, header: str) -> bool: expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, header)Worked example: sync every new lead into your CRM
- Subscribe once:
POST /v1/subscriptionswithevents: ["contact.created", "form.submitted"]. - Your endpoint verifies the signature, then upserts the contact into your CRM; on
form.submittedit also stores theanswers. - Nothing polls, nothing is missed — retries cover short outages on your side.