--- title: 'Webhook Trigger' description: 'Gives you a unique URL that external systems can POST to.' --- # Webhook Trigger Exposes a unique URL (`https://api.onehazel.com/webhook-receiver/wh_...`) that external systems — Stripe, GitHub, HubSpot, anything — can POST JSON to. Every POST that validates triggers one execution of your workflow. ## When to use - A third-party service supports webhooks and you want their events to drive a workflow. - You need to accept inbound data from a custom integration you don't have a connector for yet. - Any "when X happens in their system, do Y in ours" use case. ## Configuration | Field | Required | What it does | |---|---|---| | `expectedPayload` | No | Example JSON. Used only by the data picker so downstream nodes have something to drill into. It is *not* validated against incoming requests. | | `verificationSecret` | No | Shared secret for HMAC signature verification. When set, incoming requests must include a matching `X-OneHazel-Signature` header or the request is rejected with 401. | The `webhookId` (the `wh_...` part of the URL) is managed internally by OneHazel and displayed in the node configuration drawer. It is stable — you can paste it into the third-party system's webhook settings and it won't change unless you delete the node. ## Signature verification When `verificationSecret` is set, the receiver computes `HMAC-SHA256(secret, raw_body)` hex-lowercased, and compares against the `X-OneHazel-Signature` header. Requests without a matching signature get a 401 and the workflow does not fire. If the third-party system supports HMAC-SHA256 signatures with the same scheme (Stripe and GitHub both do a variation), set the secret and they'll be verified automatically. If not, leave it blank — the URL is still unguessable enough that unauthenticated POSTs are rare. ## Deduplication Every incoming request is deduplicated by `X-Request-Id` header for 24 hours. If a sender retries without bumping the ID, the receiver returns 200 but does NOT re-fire the workflow. If the sender doesn't set the header, a hash of the body is used. ## What it outputs The entire parsed JSON body is exposed to downstream nodes as `{{trigger.data.*}}`. Request headers are available at `{{trigger.headers.*}}`. ## Gotchas - **Body must be valid JSON**. Non-JSON bodies are rejected with 400. - **POST only** — GET/PUT/DELETE are rejected. - **Webhook URL is per-node** — if you delete and recreate the node you get a new URL and have to update the third-party system.