# Webhook authentication and signatures

> Combine the trigger URL, selected service-account bearer token, and optional API-key or raw-body HMAC verification safely.

Every production delivery has two mandatory credentials: the secret trigger
token in the URL and a bearer token issued for the exact service account
selected on that trigger. An optional inbound connector can add a third check.

## Mandatory service-account authentication

The selected service account needs the `webhooks` scope and an active run-as
member in the trigger's organization. Exchange its client credentials and send:

```text
Authorization: Bearer <short-lived token>
```

Using a different same-organization account is rejected with `401`. An expired
or invalid token is also rejected. Deactivating the workflow immediately makes
the trigger stop resolving and returns `404`; rotating a service-account secret
prevents future exchanges but does not revoke an already minted token before
its expiry.

## Optional API-key verification

Attach an inbound connector using **API key** and configure a dedicated header,
for example `x-provider-key`. Send it in addition to the bearer token:

```bash
curl --request POST \
  --header "Authorization: Bearer $TOKEN" \
  --header "Content-Type: application/json" \
  --header "x-provider-key: $PROVIDER_KEY" \
  --data-binary @delivery.json \
  "$HODOFLOW_BASE_URL/api/v1/webhooks/$TRIGGER_TOKEN"
```

The configured value is compared in constant time. A missing or empty secret
fails closed.

## Optional HMAC-SHA256 verification

For an HMAC connector, compute lowercase hexadecimal HMAC-SHA256 over the
**exact raw request bytes**. The default signature header is `x-signature`, but
the connector can configure another name.

```bash
SIGNATURE="$(openssl dgst -sha256 -hmac "$WEBHOOK_SIGNING_SECRET" -hex delivery.json | awk '{print $2}')"

curl --request POST \
  --header "Authorization: Bearer $TOKEN" \
  --header "Content-Type: application/json" \
  --header "x-signature: $SIGNATURE" \
  --data-binary @delivery.json \
  "$HODOFLOW_BASE_URL/api/v1/webhooks/$TRIGGER_TOKEN"
```

Do not parse and reserialize the JSON between signing and sending: whitespace,
key order, and the final newline change the bytes and therefore the digest.
`--data-binary` preserves the file bytes. Compute the signature again only when
the body changes; an unchanged retry uses the same signature and delivery ID.

## Unsupported connector modes

Basic and Bearer connector authentication cannot be used by webhook triggers.
Both need the `Authorization` header already reserved for the mandatory
service-account token. A legacy trigger configured that way fails with `422`
and `{"error":"Unsupported trigger authentication"}`; update the connector to
API key or HMAC before retrying.

All authentication failures intentionally use a small response surface:

```json
{"error":"Unauthorized"}
```

The response does not reveal whether the service account, connector value, or
signature was wrong. Use the request ID and your own redacted delivery logs to
correlate attempts; never log the trigger token, bearer token, API key, signing
secret, or full `Authorization` header.

## Next steps

- Send a [controlled first delivery](/webhook-ingestion/first-delivery).
- Design [stable delivery IDs and retry behavior](/webhook-ingestion/retries-troubleshooting).
- Rotate credentials using [Rotation and revocation](/authentication/rotation-and-revocation).
