# Message forwarding

> Send each new customer message to your own HTTPS endpoint, signed by Avox - the switch, the signing secret and its rotation, test events, retries, auto-pause and the delivery log.

Source: https://docs.theavoxai.com/connections/webhook-override

**Message forwarding** sends each new message your customers send to a WhatsApp number to your own
server, as it arrives. Avox signs every request so your server can check it really came from Avox.
Everything still shows up in the [Inbox](https://docs.theavoxai.com/inbox) as usual - forwarding is an extra copy for your own
systems.

Only **incoming customer messages** are forwarded. Delivery statuses, calls, messages your team sends
(from Avox or the WhatsApp Business app), chat history imports, template updates and Flow submissions
stay in Avox, where you can see them: statuses and events on [Logs](https://docs.theavoxai.com/logs), calls on
[Calls](https://docs.theavoxai.com/calls), Flow answers on [Flows](https://docs.theavoxai.com/flows).

## Webhook override

This section used to be called **Webhook Override**, and only saved a URL and verify token. It now
forwards messages for real, and there's no verify token to manage: Meta's webhook goes to Avox and is
checked with Avox's own token. Forwarded requests are authenticated with the signing secret.

You can set forwarding up in two places:

- **In the [setup wizard](https://docs.theavoxai.com/connections/setup)** - choose **Also send messages to my own server** on
  step 2, enter your endpoint, copy the signing secret (shown once) and send a test event. Forwarding
  switches on by itself when the connection completes, with that same secret.
- **On the connection's page afterwards** - turn the switch on, as described below.

## Who can change it

| Action | Admin | Manager |
| --- | --- | --- |
| See whether forwarding is on, the Endpoint URL and the delivery log | Yes | Yes |
| Open a delivery's **Payload** | Yes | Yes |
| See forwarded messages on the **Received** tab of [API logs](https://docs.theavoxai.com/connections/api-keys#api-logs) | Yes | Yes |
| Turn forwarding on or off, change the Endpoint URL | Yes | No |
| Create or rotate the signing secret | Yes | No |
| **Send test event** and **Resend** | Yes | No |

Managers see the switch and the Endpoint URL greyed out, with "Only an Admin can change message
forwarding." The signing secret is shown only once, to the Admin who creates or rotates it - after
that nobody can see it again, not other Admins and not the Avox team.

## Find the section

1. Open [Connections](https://docs.theavoxai.com/connections).
2. Click **Settings** on the connection.
3. Scroll to **Message forwarding**, under **Meta identifiers**.

![A connection's own page: the summary with Manage and Open in Meta, Meta identifiers, and the Message forwarding section with its switch, Endpoint URL, Save URL and Create secret.](https://docs.theavoxai.com/screens/connections/detail-light.webp)

*Message forwarding sits under Meta identifiers on the connection's page. Here it's off, with an Endpoint URL entered and no signing secret yet.*

## The fields

| Field | What it does |
| --- | --- |
| **On/off switch** | Starts or stops forwarding new customer messages. Turning it on needs an Endpoint URL, and creates the signing secret the first time. |
| **Endpoint URL** | Where Avox sends messages. "Must be https:// on the standard port and reachable from the internet. Redirects aren't followed." It also can't contain a user name or password. Click **Save URL** after changing it. |
| **Signing secret** | Starts with `whsec_` and is shown masked. Shown in full once, when it's created or rotated. Your server uses it to check the `Avox-Signature` header. Before one exists, the row reads "No secret yet - one is created when you turn forwarding on." with a **Create secret** button. |
| **Send test event** | Sends a sample message, marked `"test": true`, right away, and shows the result next to the button. |
| **Recent deliveries** | Every forwarded message with its status, HTTP code, attempts and time. **Payload** shows exactly what was sent; **Resend** sends it again. |

## Turn it on

**1. Build the endpoint**

Set up a route on your server that checks the signature and answers `200` quickly - see
[Receive messages](https://docs.theavoxai.com/avox-api/webhooks#receive-messages) for the payload and ready-made code.

**2. Enter the Endpoint URL**

Type the `https://` address of your endpoint and click **Save URL**. You see "Endpoint URL saved".

**3. Turn forwarding on**

Flip the switch. The first time, the **Your signing secret** dialog opens: "Copy it now and add it
to your server. Avox stores it encrypted and won't show it again." Copy the secret, add it to your
server (for example as `AVOX_WEBHOOK_SECRET`), then click **Done**. You see "Message forwarding is
on".

**4. Send a test event**

Click **Send test event**. **Delivered - your endpoint answered 200** means your server accepted it.
Otherwise the message says why, for example "Your endpoint answered 401".

**5. Check the log**

The test shows up under **Recent deliveries** with a **Test** badge. From now on each new customer
message appears there as it's sent.

> **Tip**
>
> Want to check your server before any real message reaches it? Click **Create secret**, add the
> secret to your server, and use **Send test event**.
> Test events work whether forwarding is on or off, as long as an Endpoint URL and a signing secret
> exist.

## What gets forwarded

Each new customer message of these types: text, image, audio, video, document, sticker, location,
shared contacts, button and list replies, quick-reply buttons from templates, and reactions. Media
arrives as Meta's media id with its type and caption, not the file itself. A reply that quotes an
earlier message includes the quoted message's id.

Forwarding is how incoming messages reach your own systems as they arrive - an
[API key](https://docs.theavoxai.com/connections/api-keys#what-a-key-can-do-and-what-it-cant) only sends and can't read
anything. (The one other way is an automation's [Send to webhook](https://docs.theavoxai.com/automation/actions#send-to-webhook)
step, which posts what that step describes to the address you give it.) Each forwarded request is
one `message.received` event for one message, and holds only these fields:

| Field | What it is |
| --- | --- |
| `id` | The event id (`evt_...`), the same on every retry and resend of that message. Also in the `Avox-Event-Id` header. |
| `type` | Always `message.received`. |
| `api_version` | The payload version, `2026-10-01`. |
| `created_at` | When Avox created the event (UTC). |
| `connection_id` | Which of your connections (WhatsApp numbers) the message was sent to. |
| `test` | Only on test events: `true`. |
| `data.message_id` | WhatsApp's id for the message (`wamid...`). |
| `data.from` | The customer's number, with a leading `+`. |
| `data.contact_name` | The customer's WhatsApp profile name, or `null`. |
| `data.timestamp` | When the customer sent it (UTC). |
| `data.type` | The message type: `text`, `image`, `audio`, `video`, `document`, `sticker`, `location`, `contacts`, `interactive`, `button` or `reaction`. |
| `data.context` | Only on a reply that quotes an earlier message: that message's id. |
| `data.text` | Text messages: the text. |
| `data.media` | Images, video, audio, stickers and documents: Meta's media id, the file type, the caption if there is one, and a document's file name. Never the file itself. |
| `data.location` | Shared locations: latitude and longitude, plus the place name and address when the customer shared a named place. |
| `data.contacts` | Shared contact cards: each card's name and phone numbers only. |
| `data.interactive` | A tap on your reply buttons or list: whether it was a button or a list row, the id you gave it, its title, and a list row's description. |
| `data.button` | A tap on a template's quick reply button: its text and payload. |
| `data.reaction` | A reaction: the id of the message reacted to, and the emoji (`null` when it was removed). |

Only the part that matches `data.type` is there (`text`, `media`, `location`, `contacts`,
`interactive`, `button` or `reaction`). Examples for every type are on
[Receive messages](https://docs.theavoxai.com/avox-api/webhooks#receive-messages).

### What's never sent

- **Anything from another number or Switchboard.** Each event is one message sent to this
  connection.
- **What your team keeps in Avox** - replies, notes, tags and categories, contact fields, or
  anything else about the contact.
- **Billing, plan or account details.**
- **Keys and secrets** - no API keys, no signing secret, and never the WhatsApp access token for
  your number.
- **Media files.** Only Meta's media id, the file type and the caption travel; download the file
  from Meta with your own access if you need it.
- **WhatsApp's raw payload** or your Meta account ids.

## What your endpoint must do

Avox sends a `POST` with a JSON body and these headers:

| Header | Value |
| --- | --- |
| `Avox-Signature` | `t=<unix seconds>,v1=<hex HMAC-SHA256>` - see below |
| `Avox-Event-Id` | The event's `id` (`evt_...`), the same on every retry and resend of that message |
| `Avox-Event-Type` | `message.received` |
| `User-Agent` | `Avox-Webhooks/1.0` |
| `Content-Type` | `application/json` |

A text message looks like this:

```json
{
  "id": "evt_5f0c9a7e1b2d4c6e8f00a1b2c3d4e5f6",
  "type": "message.received",
  "api_version": "2026-10-01",
  "created_at": "2026-10-10T10:00:02.000Z",
  "connection_id": "0b1c2d3e-4f50-4a6b-8c7d-9e0f1a2b3c4d",
  "data": {
    "message_id": "wamid.HBgLMTU1NTEyMzQ1NjcVAgASGBQzQTdGRDA2QzQ1MTk1NkU2NDhCRgA=",
    "from": "+15551234567",
    "contact_name": "Maria Lopez",
    "timestamp": "2026-10-10T10:00:00.000Z",
    "type": "text",
    "text": { "body": "Hi! Is my order on the way?" }
  }
}
```

The signature is a hex HMAC-SHA256 of `<t>.<raw body>`, using the whole signing secret (including
`whsec_`) as the key. For each request:

1. **Check the signature** over the raw body, and refuse anything older than 5 minutes. Accept the
   request if any `v1=` value matches.
2. **Answer `2xx` within 10 seconds.** Acknowledge first, then do your own work in the background.
3. **Skip repeats.** Use `Avox-Event-Id` (or `data.message_id`) to ignore an event you've already
   handled.

Every message type, the signature check in Node.js, Next.js, FastAPI and Flask, and the full retry
rules are on [Receive messages](https://docs.theavoxai.com/avox-api/webhooks#receive-messages).

## Retries and auto-pause

If your endpoint doesn't answer `2xx` within 10 seconds, Avox tries again after about 1 minute,
5 minutes, 30 minutes, 2 hours, 6 hours and 12 hours (a little randomness is added). After the
seventh failed attempt, about 21 hours after the first, the delivery is marked **Failed**. Redirects
count as failures, because they aren't followed.

If **5 deliveries in a row** end up **Failed**, Avox pauses forwarding for the connection, marks
anything still queued as **Failed**, and emails every Admin once, with the last failure reason and a
link to the connection. The section shows an amber notice: "Paused ...: your endpoint failed 5
deliveries in a row, so Avox stopped sending to it and emailed your Admins. Fix your endpoint, send a
test event, then turn forwarding back on." ([AVX-3104](https://docs.theavoxai.com/error-codes/avx-3104)).

To recover:

1. Fix your endpoint.
2. Click **Send test event** until it shows **Delivered**.
3. Turn the switch back on.
4. Use **Resend** on any message you missed.

Test events are sent once and never retried, and they never count toward auto-pause.

## Rotate the signing secret

Click **Rotate secret**. The **Rotate the signing secret?** dialog explains: "Avox starts signing with a
new secret right away. Your current secret keeps working for 24 hours (requests carry both signatures),
so you have time to update your server." Click **Rotate secret** again, then copy the new secret from
**Your signing secret** - it's shown once.

For the next 24 hours each request carries two `v1=` signatures - one made with the new secret, one
with the old - and the section reads "Your previous secret also works until ...". After 24 hours only
the new secret is used.

## Recent deliveries

The list shows this connection's forwarded messages, newest first, 50 at a time - click **Show older
deliveries** for more, or the refresh button to reload. Each row shows its status, a **Test** badge for
test events, the message id, the time, and a line with the HTTP code, the number of attempts, the next
try time and the last error.

| Status | Meaning |
| --- | --- |
| **Queued** | Waiting for its next attempt (usually a few seconds). |
| **Sending** | Being sent right now. |
| **Retrying** | The last attempt failed; the next try time is shown. |
| **Delivered** | Your endpoint answered `2xx`. |
| **Failed** | Out of retries, a test event your endpoint didn't accept, or forwarding was turned off or paused while it was queued. **Resend** to try again. |

| Error in the log | What it means |
| --- | --- |
| Your endpoint answered 401 (or another code) | Your server answered, but not with `2xx`. Often a signature check using the wrong secret. |
| Your endpoint answered 301 (redirects aren't followed) | Point the Endpoint URL at the final address. |
| Your endpoint didn't answer within 10 seconds | Answer first, then do the work in the background. |
| That host doesn't resolve / The connection was refused / The connection was dropped | Check the address and that your server is running and reachable. |
| Your endpoint's TLS certificate isn't valid | Renew or fix the certificate on your server. |
| Forwarding was turned off / Forwarding was paused | The message was still queued when forwarding stopped. Resend it once forwarding is back on. |

**Resend** (Admins only) appears on **Delivered**, **Retrying** and **Failed** deliveries while
forwarding is on. It queues the message again with a fresh set of retries - "Queued - it goes out
within a few seconds" - and keeps the same event id, so your server still recognises a repeat.

Deliveries are kept for 30 days, or your **Webhook Logs** retention on [Compliance](https://docs.theavoxai.com/compliance) if
that's shorter. With nothing forwarded yet, the list reads "Nothing forwarded yet. Turn forwarding on
to start sending new customer messages to your endpoint." (or, once forwarding is on, "...or send a
test event.").

Every forwarded message, across all your connections, is also on the **Received** tab of
[API logs](https://docs.theavoxai.com/connections/api-keys#api-logs), with filters, search by message or event id, and CSV
export.

## Turn it off

Flip the switch off. You see "Message forwarding is off". New messages stop being forwarded straight
away, and anything still queued is marked **Failed** (you can resend it later). The Endpoint URL and
signing secret are kept.

## Sending replies

This section is about incoming messages. To send messages from your server, create an
[API key](https://docs.theavoxai.com/connections/api-keys) and call the [Avox API](https://docs.theavoxai.com/avox-api). A key is send-only: it answers
each send with its outcome and never returns incoming messages, so forwarding is how they reach
your server.

## Errors

| Message | What to do |
| --- | --- |
| The endpoint URL must start with https:// / Enter a valid endpoint URL / Only https:// URLs are allowed | Use a full `https://` address. |
| That host isn't reachable from the internet / Only the standard https port is allowed / That host doesn't resolve / URLs with credentials aren't allowed | Use a public address on the standard `https` port, with no user name or password in it - private network addresses and `localhost` can't be used. For local testing, use a tunnel. |
| Enter your endpoint URL first | Add the Endpoint URL before turning forwarding on. |
| Save your endpoint URL first / Turn forwarding on (or create a signing secret) first | A test event needs a saved Endpoint URL and a signing secret. |
| Only an Admin can change message forwarding | Ask an Admin to make the change. |
| Couldn't load message forwarding. Try again. | Click **Try again** or refresh the page ([AVX-3098](https://docs.theavoxai.com/error-codes/avx-3098)). |
| Couldn't save message forwarding. Try again. | Check the URL and try again ([AVX-3099](https://docs.theavoxai.com/error-codes/avx-3099)). |
| Couldn't create a new signing secret. Try again. | Wait a few minutes and try again; your current secret keeps working ([AVX-3100](https://docs.theavoxai.com/error-codes/avx-3100)). |
| Your endpoint didn't accept the test event / Couldn't send a test event. Try again. | Read the reason shown next to the button ([AVX-3101](https://docs.theavoxai.com/error-codes/avx-3101)) and see [Troubleshooting](https://docs.theavoxai.com/avox-api/webhooks#troubleshooting). |
| Couldn't load the delivery log. Try again. | Click **Try again** or refresh ([AVX-3102](https://docs.theavoxai.com/error-codes/avx-3102)). |
| Turn message forwarding on first / That delivery is already on its way / Couldn't resend that delivery. Try again. | Turn forwarding on before resending, or wait for a queued delivery to finish ([AVX-3103](https://docs.theavoxai.com/error-codes/avx-3103)). |
