> ## Documentation Index
> Fetch the complete documentation index at: https://help.doozy.live/llms.txt
> Use this file to discover all available pages before exploring further.

# Step: Webhook

> Call an external service when a user reaches this step, to trigger your own automation from a track.

The **Webhook** step calls an external service when a participant reaches it.
Doozy sends an HTTPS request to a URL you choose, so a track can trigger your own
automation: provision an account, notify a CRM, kick off a Zapier or Make
scenario, or hand off to an internal tool, all at the right moment in someone's
journey.

## 🧩 How it works

A webhook has two parts that are set up separately:

1. **The webhook itself** lives in your workspace settings and holds the URL,
   custom headers, and any secrets. It's reusable across many tracks.
2. **The Webhook step** in a track just points at one of those webhooks by name.

This split means credentials live in one place. Rotate a key once and every track
using that webhook picks it up, and someone building a track can choose a webhook
without ever seeing its secrets.

<Note>
  **Roles.** Only **owners** can create or edit webhooks. Owners and
  **managers** can pick an existing webhook in a track step. Members can't use
  webhooks.
</Note>

## ⚙️ Setting it up

<Steps>
  <Step title="Create the webhook">
    Go to **Settings → Workspace → API & Webhooks** and add a webhook. Give it a
    name, paste the destination **URL** (must be `https`), and add any
    **headers** your receiver needs (for example an `Authorization` header).
  </Step>

  <Step title="Mark sensitive headers as secret">
    Toggle **Secret** on any header carrying a credential. Secret values are
    stored encrypted, shown as `***` afterward, and never revealed again. To
    change one, re-enter it.
  </Step>

  <Step title="Add a Webhook step to your track">
    In the track builder, add a **Webhook** step where you want it to fire and
    pick the webhook you created.
  </Step>
</Steps>

<Tip>
  Test against a tool like [webhook.site](https://webhook.site) or your staging
  endpoint first, so you can see the exact request Doozy sends before pointing
  it at production.
</Tip>

## 📦 What Doozy sends

When a participant reaches the step, Doozy sends a `POST` with a JSON body:

```json theme={null}
{
  "event": "track.step.fired",
  "delivery_id": "wf_123:inst_456:step_789",
  "fired_at": "2026-06-23T09:30:00.000Z",
  "workflow": { "id": "wf_123", "name": "New Hire Onboarding" },
  "step": { "id": "step_789", "title": "Provision laptop" },
  "instance": { "id": "inst_456" },
  "user": {
    "doozy_user_id": "usr_abc",
    "slack_user_id": "U01ABC",
    "slack_team_id": "T01XYZ"
  }
}
```

| Field         | Description                                                                                 |
| ------------- | ------------------------------------------------------------------------------------------- |
| `event`       | Always `track.step.fired` today. Branch on this so new event types don't break your parser. |
| `delivery_id` | Stable id for this delivery, reused across retries. Use it to deduplicate.                  |
| `fired_at`    | When the step fired (ISO 8601, UTC).                                                        |
| `workflow`    | The track's id and name.                                                                    |
| `step`        | The step's id and title (title can be empty if the step is unnamed).                        |
| `instance`    | The id of this participant's run through the track.                                         |
| `user`        | The participant's Doozy user id and Slack user/team ids.                                    |

### Headers Doozy always sets

These are added to every delivery and can't be overridden by your custom headers:

| Header                | Value                                   |
| --------------------- | --------------------------------------- |
| `Content-Type`        | `application/json`                      |
| `Idempotency-Key`     | The `delivery_id` (same across retries) |
| `X-Doozy-Delivery-Id` | The `delivery_id`                       |

<Note>
  Because the `Idempotency-Key` is the same on every retry of the same delivery,
  a receiver that keys on it will process each step firing **once**, even if
  Doozy retries.
</Note>

## 🔁 Delivery and retries

| Behavior    | Detail                                                              |
| ----------- | ------------------------------------------------------------------- |
| Method      | `POST` with the JSON body above                                     |
| Success     | Any `2xx` response                                                  |
| Timeout     | 10 seconds per attempt                                              |
| Retries     | Up to **3 attempts** total                                          |
| Retried     | `5xx` responses and network errors or timeouts                      |
| Not retried | `4xx` responses (treated as a permanent rejection) and blocked URLs |

Respond quickly with a `2xx` and do any slow work asynchronously on your side. If
your receiver needs more than a moment, acknowledge first and process after.

You can review recent attempts (status code, attempt number, and any error) in the
**delivery log** on the webhook in **Settings → Workspace → API & Webhooks**.

## 🔐 Security

* **HTTPS only.** Plain `http` URLs are rejected.
* **No internal targets.** Doozy blocks requests to private, loopback, link-local,
  and cloud-metadata addresses.
* **Encrypted secrets.** Secret header values are encrypted at rest and never shown
  again after you save them.

## ⭐ Best Practices

1. **Return `2xx` fast** and process asynchronously, so a slow job doesn't time out
   the delivery.
2. **Deduplicate on the `Idempotency-Key`**, since a retry can deliver the same
   step firing more than once.
3. **Authenticate the request** with a secret header (for example a bearer token)
   so only Doozy can call your endpoint.
4. **Reserve one webhook per destination** and reuse it across tracks, so rotating
   a credential is a single change.

## 🎯 Common Use Cases

* **Provisioning**: Create an account or grant access in an external system at the
  right step.
* **CRM and data sync**: Push a signal to your CRM or data warehouse when someone
  reaches a milestone.
* **No-code automation**: Trigger a Zapier or Make scenario to fan out to other
  tools.
* **Internal tooling**: Kick off a job in your own service as part of onboarding or
  offboarding.

## 🔗 Related Steps

* [Step: Message](/tracks/steps/message): Tell the participant what happened after a webhook fires
* [Step: Delay](/tracks/steps/delay): Wait before or after calling an external system
* [Steps Overview](/tracks/steps/overview): All the step types you can use in a track
