> For the complete documentation index, see [llms.txt](https://docs.baas.sh/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.baas.sh/baas-console/automations/triggers/webhook.md).

# Webhook

Start an automation from any external system by calling its unique URL with a secret header.

A webhook trigger gives your automation a unique URL: any system that can send an HTTP request can start it. In this guide, an e-commerce backend calls the webhook when an order is paid, and the automation transfers the customer's reward tokens and emails them a confirmation.

## Set up the webhook

{% stepper %}
{% step %}

### Pick the Webhook type

On the **Pipeline** tab, open the [trigger configuration](/baas-console/automations/triggers.md) and pick **Webhook**. There is nothing to fill in: the URL and its secret are created when you save, and the secret is shown only once.
{% endstep %}

{% step %}

### Save

Click **Save**. The panel stays open and now shows your webhook's credentials.
{% endstep %}

{% step %}

### Copy the URL and the secret

Two fields appear: **URL** and **Signing secret**. Copy both.
{% endstep %}
{% endstepper %}

{% hint style="warning" %}
**The secret is shown once.** Store it in your backend's secret manager before closing the panel. If you lose it, [rotate it](#rotate-the-secret).
{% endhint %}

## The invoke URL

```
https://<project-id>-api.baas.sh/triggers/webhooks/<webhook-id>/invoke
```

The base is your project's API URL, listed under **Project Endpoints** on the Console's Dashboard page (see the [Console overview](/baas-console/console.md)). The **URL** field gives you the complete address, ready to copy.

## Call the webhook

Send a POST request with a JSON body, and pass the secret as-is in the `x-webhook-secret` header (secrets start with `wh_`). Leave out `Authorization` and `apikey` headers: the endpoint rejects them.

```bash
curl "https://<project-id>-api.baas.sh/triggers/webhooks/<webhook-id>/invoke" \
  -H "Content-Type: application/json" \
  -H "x-webhook-secret: wh_..." \
  -d '{"orderId": "1234", "customerEmail": "ada@example.com", "amount": "49.90"}'
```

The body is any JSON object, up to 8 MiB, and your actions receive it exactly as sent. An empty body works too, and starts the run with nothing to read. Query parameters are ignored, so put everything your actions need in the body.

The response is the run itself:

```json
{ "runId": "...", "workflowId": "...", "status": "queued", "startedAt": null, "finishedAt": null }
```

`status` is one of `queued`, `running`, `success`, or `failed` (`success` is what the [Runs tab](/baas-console/automations/runs-and-logs.md) shows as **Completed**). `startedAt` and `finishedAt` fill in as the run progresses.

{% hint style="info" %}
**Retries are safe.** Resending an identical request returns the same run instead of creating a duplicate.
{% endhint %}

To start a new run on every call, include something unique in the body, like the `orderId` above. A webhook called repeatedly with an empty body records only one run.

## What actions receive

The body arrives under `trigger.body`, key for key. With the body above, an action reads `{{trigger.body.orderId}}` for the order and `{{trigger.body.customerEmail}}` to address the confirmation [email](/baas-console/automations/actions/email.md). The [Trigger object](/baas-console/automations/reference/trigger.md) reference details the shape your actions read.

## Rotate the secret

The **Rotate** button, next to the secret in the trigger's **Edit** panel, generates a new secret, shown once like the first.

{% hint style="warning" %}
**Rotation is immediate.** Calls with the old secret fail from the moment you rotate, so copy the new secret and update your callers right away.
{% endhint %}

## If the call fails

| Status                 | Why                                                                                                                 |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------- |
| **401**                | Missing, duplicated, or wrong `x-webhook-secret`, or an `Authorization` or `apikey` header was sent                 |
| **400**                | The trigger or automation is inactive, or no action in the pipeline is active                                       |
| **400**                | The body is not a JSON object, is over 8 MiB, or is non-empty but was sent without `Content-Type: application/json` |
| **404**                | The webhook id is well-formed but unknown (a malformed id is a 400)                                                 |
| **503**                | Retry the identical request shortly (`code: "retry_same_request"`)                                                  |
| **429**, other **5xx** | Transient: retry the identical request with a bit of backoff                                                        |

A **4xx** other than 429 is not worth retrying: see [when runs are recorded](/baas-console/automations/runs-and-logs.md#when-runs-are-recorded). A retry of the identical request resolves the same run rather than creating a second one.

## Next

* [Runs and logs](/baas-console/automations/runs-and-logs.md): follow each call's run and read its logs.
* [Triggers](/baas-console/automations/triggers.md): back to the section overview.
