POST to
your server whenever something happens in your operation: a message arrives, a contact is
created, a field is filled in by the agent.
Each company has at most one webhook. It’s not a collection: you configure one URL and
choose which events it receives. Trying to create a second one returns
409.Configure
POST /v1/webhooks with the URL and the list of events. Required scope: webhooks:create.
What Nuvia sends
Every delivery is aPOST with these headers:
And the body always has the same three-field structure:
event: the event that triggered the delivery.timestamp: ISO 8601 UTC, generated when the delivery was queued.data: the resource related to the event (see the table below).
Available events
For message, conversation, and contact events,
data carries the resource in the same format the
API reference returns on the corresponding GET requests, for example,
GET /v1/messages/conversation/{id} for messages. The exceptions are FIELD_UPDATED,
MESSAGE_FAILED, and CONVERSATION_STEP_CHANGED, detailed below.
Treat
data tolerantly: read the fields you use and ignore the rest. New fields may appear as
the platform evolves, and that isn’t considered a breaking change.FIELD_UPDATED is different
This event doesn’t return an entity, but a description of the change:
field.context indicates what the field belongs to: contact, conversation, or business.
value has the field’s type: string, number, boolean, or list. conversationId only comes
through when the change happened inside a conversation.
MESSAGE_FAILED covers delivery failure, not send failure
The event fires when WhatsApp returns a failure status for a message that already left Nuvia. It
applies to the WhatsApp Cloud API channel only.
The body carries the message with its updated status and a failure_reason key with the cause:
code is the WhatsApp error code, for example 131026 for a number that does not use WhatsApp and
131047 for an expired 24-hour window. message is display-ready text, the same one the operator
sees on the conversation card, and it is written in Portuguese, as the example above shows.
Use code for your integration logic and message only for display. The text may change without
notice, the code will not.
CONVERSATION_STEP_CHANGED delivers the change, not the conversation
The event fires when the conversation moves to another pipeline step, whether from the dashboard,
the AI agent, or the API. It only fires on an actual step change.
previousStep comes as null when the conversation was not in any step yet. origin indicates
what caused the change and may not come through.
This event does not replace
CONVERSATION_UPDATED. When the step changes through a PATCH on
the conversation, whoever subscribes to both receives both in the same request. Through the
dedicated step change, only CONVERSATION_STEP_CHANGED goes out.Who sent the message
Message events carry two fields that, together, tell where the message came from:
Messages sent from the WhatsApp Business app in coexistence arrive through the
MESSAGE_RECEIVED
event, not MESSAGE_SENT. So, to detect the lead’s reply, filter by sender_type: CONTACT and
message_type: INCOMING, not just by the event name.
If your integration sends messages through the API and needs to tell them apart from the AI’s
messages, store the IDs of the messages it sent itself.
Validate that the call came from Nuvia
The available mechanism iscustom_headers: register a secret value and check it on every
request.
Node.js
PUT /v1/webhooks/{id} if you suspect a leak.
Delivery guarantees
These are the actual characteristics of delivery. Read this before building anything that depends on a webhook:One attempt per event, no retry
One attempt per event, no retry
If your server is down, returns
5xx, or times out, the event is lost. The failure is
logged on Nuvia’s side, but nothing is resent. For critical data, reconcile periodically with
the API (for example, by listing a conversation’s messages) instead of relying solely on the
webhook.10-second timeout
10-second timeout
Nuvia waits at most 10 seconds for your response. Respond
2xx immediately and process in the
background. If you process before responding, legitimate deliveries turn into timeouts.Order isn't guaranteed
Order isn't guaranteed
Status events come from WhatsApp, which may deliver them out of order (a
MESSAGE_DELIVERED
arriving after the MESSAGE_READ for the same message). Nuvia doesn’t reorder them. Treat each
event by the state it carries, without assuming sequence.Status events only fire on a real transition
Status events only fire on a real transition
MESSAGE_DELIVERED and MESSAGE_READ are only sent when the status actually changes. WhatsApp
resends the same status multiple times, and those repeats are discarded: you don’t get a
duplicate because of it. Still, write an idempotent handler: use the message identifier as the
key.Nothing is delivered while the webhook is inactive
Nothing is delivered while the webhook is inactive
With
status: "INACTIVE", or for events you didn’t include in events, Nuvia doesn’t queue
any delivery, and there’s no history to recover afterward. Reactivating doesn’t recover the
time it was stopped.Manage the configuration
The
GET returns a single object with the company’s configuration, or null if there is
none. It’s not a list. The id used in PUT and DELETE comes from that object. After removal,
the company can create a new webhook again.
To pause temporarily without losing the configuration, prefer PUT with status: "INACTIVE"
instead of deleting.
Next steps
Integration examples
The webhook inside a complete use case, with an automatic reply.
API reference
Parameters and responses for the webhook endpoints.