# Webhook triggers

> How APIANT webhook triggers receive HTTP requests, the four ways a webhook reaches an automation, what the caller gets back, duplicate suppression, and what happens while an automation is off.

A webhook trigger starts a run when an external system sends an HTTP request to a URL APIANT generates for that trigger. The run starts as soon as the request is received, with no polling delay.

## How a webhook reaches an automation

Apps deliver webhooks in different ways, and the app's trigger in the catalog decides which one applies:

| Delivery | Who registers the URL | How it works |
|---|---|---|
| Manual | You, in the other app's settings | APIANT generates a URL per trigger. You paste it into the app's webhook settings. A generic webhook trigger works this way for any system that can send an HTTP request. |
| Self-registering | APIANT, when you turn the automation on | APIANT calls the app's API to create the webhook on activation and delete it on deactivation. |
| Per connected account | The app, once per connection | One URL receives every event for a connected account. APIANT starts every active automation whose webhook trigger uses that connection. |
| Shared app endpoint | The connector builder, once per app | For apps that allow only one callback URL for the whole app. The connector reads each delivery and routes it to the right account. |

## Getting the webhook URL

Ask Claude or the assistant for the automation's webhook URL. Claude returns it when it builds or saves an automation with a webhook trigger, and returns one URL per webhook trigger when there are several. Copy the URL exactly as given; do not assemble or edit one by hand. A URL that has been altered or copied incompletely matches no automation and receives a `404`. The URL needs no sign-in, so share it only with the system that sends the events.

## Sending a request

| Method | Result |
|---|---|
| `POST`, `PUT`, `GET` | Starts a run |
| `HEAD` | Empty response, no run |
| `OPTIONS` | CORS preflight response, no run |
| Any other method | `405` |

```bash
$ curl -X POST "<webhook URL from Claude>" \
    -H "Content-Type: application/json" \
    -d '{"order_id": 10482, "customer": {"email": "dana@example.com"}, "total": 129.5}'
```

### Responses

| Status | Meaning |
|---|---|
| `200`, empty body | The request was accepted. The run happens afterwards, so the response does not say whether the run succeeded. |
| `404` | The path matches no active account or automation, or the automation was deleted. |
| `413` | The body is larger than the platform's inbound limit (32 MB by default). |
| `503` | The platform is in maintenance. |

A caller that needs the automation's result in the response should use a web service trigger instead. See [Web service automations](/docs/interfaces/web-services).

### Verification handshakes

Two common vendor handshakes are answered automatically, without starting a run:

- A request carrying an `X-Hook-Secret` header gets the header echoed back with `200`.
- A request whose body is only `{"challenge": "..."}` gets the challenge echoed back as JSON.

## What the steps receive

| Field | Contains |
|---|---|
| `trigger.body` | The request body. JSON is parsed, so nested fields are read as `trigger.body.customer.email`. A non-JSON body is the raw string. |
| `trigger.params` | Query string parameters by name |
| `trigger.query_string` | The raw query string |
| `trigger.content_type` | The request's content type |
| `trigger.path` | The request path |

Request headers are not passed to the steps.

With the request above, a step setting maps the customer's email like this:

```json
"settings": {
  "Email": "{{ trigger.body.customer.email }}"
}
```

When an app's payload shape is unknown, turn the automation on, send one real event from the app, then ask Claude to read that run and map the fields it finds. See [Field mapping](/docs/automations/field-mapping).

## Duplicate deliveries

Vendors often send the same webhook more than once. APIANT suppresses a delivery whose body is byte-identical to one the same automation already processed successfully in the last 10 minutes.

- Suppression starts only after a run succeeds, so a vendor's retry of a delivery that failed still gets through.
- Suppressed duplicates are counted on the original run under **Suppressed** in Execution History.
- To turn suppression off for one automation, for example when identical bodies are legitimate separate events, ask Claude to disable webhook duplicate suppression for it.

## While the automation is off

Deliveries to an automation that is turned off are kept, not run. When you turn the automation back on, APIANT replays the deliveries it kept.

- Before turning an automation back on after a long pause, decide whether those queued events should run. **Processing > Reset…** in the gear menu discards unprocessed webhooks. It also clears the automation's trigger state, pending automatic retries, and lookup table rows saved with **This automation** scope.
- Deliveries already queued when an automation is deleted are discarded. Later deliveries receive `404`.
- From Claude Code, Claude can list the deliveries that arrived for an automation but never ran. Ask "show unprocessed webhooks for this automation".

## Self-registering webhooks

A self-registering trigger manages the webhook in the other app for you:

- **Turning the automation on** creates the webhook in the app. If the app refuses, the automation stays off and the error is returned.
- **Turning it off** deletes the webhook in the app.
- **Deleting the automation** deletes the webhook first.
- If the delete call to the app fails, APIANT keeps its record of the registration so the cleanup can be retried. An hourly task removes registrations that no automation needs any more.

## Per-account and shared endpoints

A per-account webhook URL identifies a connection, not an automation. Every active automation with a webhook trigger on that connection runs for each delivery, so each automation filters for the events it cares about with a condition step. See [Conditions, loops and parallel branches](/docs/automations/control-flow).

A shared app endpoint is set up once by whoever builds the connector. Customers using the connector do not handle that URL. See [Build a connector, action or trigger](/docs/apps/custom-connectors).

## Troubleshooting

| Symptom | Likely cause | What to do |
|---|---|---|
| The caller gets `200` but no run appears | The automation is off, so the delivery was kept for later, or the body duplicated a recent successful delivery | Turn the automation on, or check the original run's **Suppressed** count |
| The caller gets `404` | The URL was typed by hand, copied incompletely, or belongs to a deleted automation | Ask Claude for the URL again |
| A burst of old events runs after turning the automation on | Deliveries kept while it was off were replayed | Use **Reset…** before turning on if those events should not run |
| Turning on fails for a self-registering trigger | The app rejected the webhook creation, often a connection permission | Reconnect the app with the required access, then turn on again |
| Fields map to empty values | The payload shape differs from the mapping, or field names differ in case | Ask Claude to read the last run's trigger data and fix the mapping |

## Next steps

- [Testing an automation](/docs/automations/testing)
- [Field mapping](/docs/automations/field-mapping)
- [Retries, replays and restarts](/docs/runs/retries-and-replays)
- [Public URLs](/docs/reference/urls)
