# Custom webhook trigger

> Bring your own HTTP webhook: run a workflow from any system that can POST JSON, with Cotera token auth or your own signature verification.

Source: https://cotera.co/docs/reference/webhooks/custom

---

When the source isn't one of the built-in providers, a **Custom webhook** accepts any HTTP POST. Cotera mints a unique URL for the trigger; anything that can POST JSON to it runs your workflow with the request body as input. No provider account, no subscription to register — you own the endpoint.

## Event

A custom webhook has no event catalog. Every POST that authenticates is passed straight through — the raw JSON body becomes the workflow's input, unwrapped. There's no per-event filtering, so filter inside the workflow if the same URL carries more than one kind of event.

Each custom webhook is independent: it has its own URL and secret and is never deduplicated onto another custom webhook, so one delivery never fans out to other workflows in your org.

## Authentication

A custom webhook verifies deliveries in one of two profiles.

### Token auth (default)

Send the trigger's secret as a bearer token. Cotera accepts it in any of these places, so you can use whichever your sender supports:

| Where                           | Example                                                    |
| ------------------------------- | ---------------------------------------------------------- |
| `Authorization` header          | `Authorization: Bearer <secret>`                           |
| `X-Cotera-Webhook-Token` header | `X-Cotera-Webhook-Token: <secret>`                         |
| Query parameter                 | `?cotera_webhook_token=<secret>` or `?auth_token=<secret>` |

```bash
curl -X POST 'https://app.cotera.co/api/triggers/webhooks/<trigger-id>' \
  -H 'Authorization: Bearer <secret>' \
  -H 'Content-Type: application/json' \
  -d '{ "order_id": "1234", "status": "paid" }'
```

For backward compatibility, a sender that instead HMACs the body into an `X-Cotera-Signature-256: sha256=<hex>` header is still accepted when no valid token is present.

### Custom signing

If your sender signs requests with its own scheme, configure the custom profile to match it. Cotera checks the signature against the trigger's secret using the algorithm you pick:

| Verification                     | How the signature is computed                                                                   |
| -------------------------------- | ----------------------------------------------------------------------------------------------- |
| `cotera` / `sha256_hex_prefixed` | `sha256=<hex>` — HMAC-SHA256 of the raw body (e.g. GitHub's `X-Hub-Signature-256`).             |
| `hex_v0_prefixed`                | `v0=<hex>` — HMAC-SHA256 of `v0:{timestamp}:{body}`; requires a timestamp header (Slack-style). |
| `hmac_sha256_base64`             | Base64 HMAC-SHA256 over a chosen concatenation of the request parts.                            |

Alongside the algorithm you configure:

* **Signature header(s)** — which header carries the signature (defaults to `x-cotera-signature-256`).
* **Timestamp header** — required for `hex_v0_prefixed`; optional otherwise.
* **Signed payload** — for `hmac_sha256_base64`, which parts are concatenated before signing: any of `body`, `timestamp`, `method`, `uri`.
* **Max timestamp skew** — how far the signed timestamp may drift from now before the delivery is rejected.

## Testing

Use the trigger's **Test webhook** button to send a signed sample POST through the full pipeline. Cotera signs the test delivery to match whichever profile the trigger is configured with, so a passing test confirms your verification settings before the real sender is wired up.

