# Webhook ingestion

> Send authenticated inbound deliveries to saved, active webhook workflows and follow the runs they create.

Webhook ingestion turns one authenticated inbound `POST` into one workflow run.
It is for sending events **into** Hodoflow; it is not an outbound event-webhook
product.

```text
POST /api/v1/webhooks/:token
```

The token comes from a saved webhook trigger. It resolves one active trigger in
one organization and must be treated as a secret. The delivery must also carry a
bearer token minted from the **exact service account selected on that trigger**.
That account needs the `webhooks` scope.

## Before the first delivery

Webhook ingestion needs product state that the JSON:API quickstart does not:

1. create a service account with `webhooks` in the intended workspace;
2. create a workflow with a webhook trigger and select that service account;
3. add at least one valid workflow step, save the workflow, and activate it;
4. copy the token-only URL shown on the active workflow; and
5. exchange the selected account's client credentials for a short-lived bearer
   token.

Follow [Send your first webhook delivery](/webhook-ingestion/first-delivery) for
the complete setup, request, accepted response, and run lookup.

## Delivery contract

Send JSON and a stable delivery identifier:

```bash
curl --silent --show-error \
  --request POST \
  --header "Authorization: Bearer $TOKEN" \
  --header "Content-Type: application/json" \
  --header "Idempotency-Key: $DELIVERY_ID" \
  --data-binary @delivery.json \
  "$HODOFLOW_BASE_URL/api/v1/webhooks/$TRIGGER_TOKEN"
```

An accepted delivery returns `202`, but the workflow has not necessarily
finished:

```json
{
  "status": "accepted",
  "workflow_run_id": "550e8400-e29b-41d4-a716-446655440000"
}
```

Use the returned ID to read the run through JSON:API. A repeated delivery with
the same stable identifier returns `202` and `{"status":"duplicate"}` without
creating a second run.

## Payload behavior

The JSON request becomes the workflow payload. If the top-level document has a
`payload` member, only that member's value is used as the workflow payload;
otherwise the whole document is used.

The default body limit is 1 MiB. The default per-trigger rate is 100 requests in
a 60-second fixed window. Deployments can configure both values, so clients must
honor `413` and the `Retry-After` header on `429` rather than assuming the
defaults.

## Security choices

The trigger URL and bearer token are separate credentials. Possession of only
one is insufficient. An attached inbound connector may add API-key or
HMAC-SHA256 verification; Basic and Bearer connector modes are rejected because
they conflict with the mandatory service-account `Authorization` header.

Read [Authentication and signatures](/webhook-ingestion/authentication-signatures)
before integrating a signed sender. Read
[Deduplication, retries, and troubleshooting](/webhook-ingestion/retries-troubleshooting)
before implementing retry behavior.

## Reference and next step

- [Webhook endpoint reference](/reference/webhooks/ingest)
- [Token exchange reference](/reference/authentication/token-exchange)
- [Triggers and webhooks product guide](/help/workflows/triggers-and-webhooks)

Next, [send a controlled first delivery](/webhook-ingestion/first-delivery) and
verify the returned workflow run.
