> ## Documentation Index
> Fetch the complete documentation index at: https://docs.my-aichatbot.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Connect your endpoint, pass the check, and handle retries safely.

Your webhook is where we hand you the conversation. Every visitor message, button click, and form answer arrives there as a signed POST.

## What your endpoint needs

* **HTTPS only.** Plain `http://` is refused.
* **A public address.** Private, local, and cloud-internal addresses are blocked for security.
* **No redirects.** A `3xx` is a hard fail. We never follow it.
* **Answer within 10 seconds.**

## Verification is required

A webhook bot **cannot go active until its URL passes a check**. Changing the URL clears the check — you verify again. This stops a typo from quietly eating every customer message.

Press **Verify** and we send a `webhook.verify` event:

```json theme={null}
{
  "api_version": "2026-07-01",
  "event": "webhook.verify",
  "event_id": "evt_01J9",
  "created_at": "2026-07-17T02:11:09.412Z",
  "test": true,
  "workspace_id": "b6a2",
  "chatbot": { "id": "c1d4", "name": "Acme Support Bot" },
  "challenge": "3f9a8c2e",
  "verification_code": "XXXX-XXXX",
  "response_url": null
}
```

Send the `challenge` back. Two forms work:

<CodeGroup>
  ```json As JSON theme={null}
  { "challenge": "3f9a8c2e" }
  ```

  ```text As the raw body theme={null}
  3f9a8c2e
  ```
</CodeGroup>

We are forgiving here on purpose: `Content-Type` does not matter, extra spaces are fine, and one pair of wrapping quotes is allowed — which covers Make's habit of quoting values. Extra JSON keys are fine too, which covers n8n's Respond node.

<Warning>
  The key name is case-sensitive. `{"Challenge": "…"}` fails.
</Warning>

A check is **never retried** — it runs once per press, with a 10-second limit. Just press it again.

### When it fails

| Reason          | What it means                                                         |
| --------------- | --------------------------------------------------------------------- |
| `unreachable`   | Network error, or a non-2xx status.                                   |
| `timeout`       | No answer within 10 seconds.                                          |
| `bad_challenge` | `2xx`, but the echo did not match. The wizard shows what we received. |
| `redirect`      | You returned a `3xx`. We show the `Location`, but we never follow it. |
| `ssrf_blocked`  | The address points somewhere we do not allow.                         |

<Note>
  Zapier Catch Hooks cannot return a custom body, so we skip the echo for `zapier.com` and `*.zapier.com` — any `2xx` counts. We match the real hostname, so look-alikes such as `zapier.com.example.org` do not qualify. Instead, you paste the `verification_code` into the wizard within 30 minutes. See the [Zapier guide](/integrations/zapier).
</Note>

## Every request carries these headers

| Header                     | Example           | Use                                                         |
| -------------------------- | ----------------- | ----------------------------------------------------------- |
| `X-MyAIChatbot-Signature`  | `v1=6e07b5c9`     | Prove the request is real. **Always check this.**           |
| `X-MyAIChatbot-Timestamp`  | `1752721512`      | Unix seconds. 300-second replay window.                     |
| `X-MyAIChatbot-Event`      | `message.created` | Route without reading the body.                             |
| `X-MyAIChatbot-Event-Id`   | `evt_01J9ZW9K7Q`  | **The repeat-spotting key.** Never changes between retries. |
| `X-MyAIChatbot-Attempt`    | `2`               | Try number. `1` is the first, inline try.                   |
| `X-MyAIChatbot-Chatbot-Id` | `c1d4`            | Lets one endpoint serve many bots.                          |

## Retries and repeats

If a try fails in a way we can retry — a timeout, a network error, a `408`, `425`, `429`, or any `5xx` — we try up to **4 times in total**:

| Try | When                                                               |
| --- | ------------------------------------------------------------------ |
| 1   | Right away, in the same request                                    |
| 2   | About 15 seconds later                                             |
| 3–4 | Spaced out by our queue — about a minute, then a couple of minutes |

Other `4xx` responses and redirects fail at once, with no retry.

<Warning>
  **Spot repeats with `X-MyAIChatbot-Event-Id`.** It is the same on every try of one event, and the body is re-sent byte for byte, with only a fresh signature time. Without this check, one slow response can charge a card twice.
</Warning>

<Note>
  Do not read `delivery_attempt` from the body — it is always `1`. The `X-MyAIChatbot-Attempt` header is the real counter.
</Note>

## Order

Messages carry a per-conversation `seq` number that only goes up. Delivery order is **not** promised — a visitor sending two quick messages can cause two overlapping webhook calls. If order matters, sort by `message.seq`, not arrival time.

## Testing

The **Test webhook** button sends a canned event with `"test": true`, waits up to 10 seconds, and shows you the status, the speed, and exactly what we understood from your reply — including any problems. Test events are logged, but never retried, and they never touch a real conversation.

<Tip>
  Check `test` before doing anything you cannot undo. Both `webhook.verify` and `webhook.test` set it.
</Tip>

## Delivery log

Every try is recorded: status, event type, try count, response status, speed, and the first 2 KB of your reply. Failed deliveries can be retried by hand.

Statuses you will see:

| Status                    | Meaning                                             |
| ------------------------- | --------------------------------------------------- |
| `succeeded`               | Delivered and accepted.                             |
| `delivered_with_warnings` | Accepted, but something was trimmed or skipped.     |
| `invalid_response`        | You returned `2xx` with Block Kit we could not use. |
| `rejected_inbound`        | Your `response_url` or API call was rejected.       |
| `retrying`                | A retryable failure. More tries queued.             |
| `failed`                  | Out of tries, or a hard error.                      |
| `cancelled`               | A human took over while it was in flight.           |

## Next

<CardGroup cols={2}>
  <Card title="Check signatures" icon="shield-check" href="/receiving-events/verifying-signatures">
    Do not skip this one.
  </Card>

  <Card title="Event reference" icon="list" href="/receiving-events/event-reference">
    Every event and its payload.
  </Card>
</CardGroup>
