> For the complete documentation index, see [llms.txt](https://docs.notionapps.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.notionapps.com/automation/webhooks-and-integrations/webhook-technical-reference.md).

# Webhook Technical Reference

Canonical maker page for inbound and outbound HTTP. Not sure which product? Start with [Workflow vs Messaging Webhooks](https://docs.notionapps.com/automation/webhooks-and-integrations/workflow-vs-messaging-webhooks). Admin kill switches, host env files, and internal endpoints stay in repo `docs/` — not here.

#### What this is / when to use it

| Direction    | Meaning                                                     | NotionApps surface                                                                                         |
| ------------ | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| **Inbound**  | An external tool POSTs JSON into NotionApps and starts work | Workflow trigger `webhook_received`, or Messaging inbound that publishes a message                         |
| **Outbound** | NotionApps POSTs JSON to a URL you own                      | Notify (fire-and-forget), **Call webhook and wait** (sync JSON), or a Messaging subscriber of type Webhook |

Use webhooks when another system is the first place work begins (CRM, payments, a form tool), when a downstream system must hear the result, or when a button must **wait for Make** and then refresh the already-open tab.

When **not** to: you only need email. Use `send_notification`. You are still exploring — start with the [wizard](https://docs.notionapps.com/automation/start-from-a-wizard), not HTTP.

#### Before you start

1. Workflow entitlement is on (Messaging **External API** level if you use Messaging webhooks).
2. You have the **application id** from the builder URL.
3. You can generate a **webhook secret** in the workflow builder when the trigger is **Webhook received**. Copy it once — it is not shown again.
4. You have an outbound catcher ([webhook.site](https://webhook.site), Hookdeck, Zapier Catch Hook) if you will test outbound.
5. The workflow is **published / active**. Drafts can accept HTTP and create **zero** runs.

#### Create a webhook secret

The secret belongs to **this app**. Workflow inbound and Messaging inbound both use it.

1. Open **Automation → Workflows**.
2. Select (or create) a workflow whose trigger is **Webhook received**.
3. Open **External webhook intake**.
4. Enter a name you will recognize later, for example `Make production`.
5. Choose an **active term**: 30, 90, 180, or 365 days, or **No expiry**.
6. Click **Generate secret**.
7. Copy the value immediately. Paste it into Make, Zapier, or your sender as:

```
x-notionapps-webhook-token: {PASTE_THE_SECRET}
```

The builder also accepts `Authorization: Bearer {SECRET}` and `x-workflow-webhook-token: {SECRET}`.

The list then shows only a **prefix** (for example `na_wh_8f3a2c1b`), the name, status, days left, and last used. The full secret is never shown again.

{% hint style="warning" %}
After this app has at least one **active** secret, the old shared platform token is rejected for that app. Generate the secret and update Make/Zapier before you revoke the previous value.
{% endhint %}

**Rotate**

1. Generate a new secret (you can have up to **five** active secrets).
2. Update Make, Zapier, or the sender to the new value.
3. Confirm a test POST succeeds.
4. **Revoke** the old secret.

You cannot revive a revoked secret. Generate a new one instead.

**Rules**

* A secret for App A cannot post into App B.
* Revoked or expired secrets return 401.
* If inbound webhooks are disabled for the app, generate is blocked.
* Do not put the secret in a Notion property that guests can open.

#### Build it

**Webhook intake template (recommended first route)**

Automation → Workflows → Start from a template → **Webhook intake**, or recreate:

| Order | Step                       | Purpose                                 |
| ----- | -------------------------- | --------------------------------------- |
| 1     | Condition                  | e.g. `payload.status` equals `approved` |
| 2     | Create record              | Map `payload.name`, `payload.email`     |
| 3     | Notify (channel = Webhook) | Outbound proof to your catcher          |
| 4     | End                        | Done                                    |

Trigger: `webhook_received` on the application.

Replace example field names with your Notion properties. Full step list: [Step catalog](https://docs.notionapps.com/automation/advanced-reference/step-catalog).

**Inbound URL shapes**

```
POST {API_HOST}/v1/webhooks/workflow-events
POST {API_HOST}/v1/application/{APPLICATION_ID}/workflows/webhook-events

POST {API_HOST}/v1/webhooks/messaging-events
POST {API_HOST}/v1/application/{APPLICATION_ID}/messaging/webhook-events
```

`{API_HOST}` is the API for your environment (production `https://api.notionapps.com` unless support tells you otherwise). Prefer the **app alias** URL from the builder so the event is already scoped.

**Auth (maker)**

Use the secret you generated above. Do not guess a platform token and do not reuse a secret from another app.

If a request returns 401/403, the secret is wrong, revoked, expired, or bound to a different app. If it returns 503 about a token not configured, that is an environment problem — contact support.

**Test inbound**

1. Publish the workflow.
2. POST JSON with a unique `event_id` / `idempotency_key` every try (retries can look like “nothing happened”).
3. Example body:

```json
{
  "application_id": "YOUR_APP_ID",
  "event_id": "test-2026-08-31-001",
  "payload": {
    "status": "approved",
    "name": "Sample Lead",
    "email": "user@example.com"
  }
}
```

4. Expect an accepted response and `runs_created >= 1` when the trigger matched.
5. Confirm the Notion row and, if you added Notify, the catcher POST.

**Test outbound**

1. Notify step → Channel **Webhook** → paste the catcher URL.
2. Subject/body you will recognize.
3. Fire inbound (or Simulate if Simulate sends outbound in your build).
4. Confirm the catcher shows headers + body.

**Call webhook and wait (open-tab refresh)**

Use **Call webhook and wait** when a button must POST Make (or another webhook), receive JSON, then Refresh or Open screen on the **same open tab**. This is not fire-and-forget Notify webhook.

1. Add the step and paste the Make webhook URL.
2. Leave the sync timeout at 8000 ms unless you need a shorter wait (max 12 seconds on the tap).
3. In Make, end with **Webhook response** and return JSON, for example `{ "ok": true, "cart_record_id": "…" }`.
4. Map a later Open screen record id to `context.<this_step_id>.cart_record_id` when Make returns one.
5. Follow with Refresh / Open screen / Show message.

What the user sees:

* Make answers within 8 seconds: the following UI steps run on the open tab.
* Make is still going: Working… then the app polls the run for 45 seconds.
* Still pending: “This is taking longer. Refresh to see the cart.”
* 4xx / 5xx in the sync window: error, no refresh, no retry.

**Live screen updates** must be on for the app, or Refresh after this step will not update the open tab. The switch is off by default. NotionApps turns it on from Workflow Operations → Controls.

Inbound webhooks Make fires back at NotionApps still cannot refresh an already-open tab.

#### Every control

| Control                          | Where                   | What it does                                                          |
| -------------------------------- | ----------------------- | --------------------------------------------------------------------- |
| Trigger Webhook received         | Workflow                | Starts a run from HTTP.                                               |
| Intake URL (central / app alias) | Builder                 | Where senders POST.                                                   |
| **Generate secret**              | External webhook intake | Creates a per-app secret. Plaintext is shown once.                    |
| Name + active term               | External webhook intake | Label and 30 / 90 / 180 / 365 days or no expiry.                      |
| Revoke                           | External webhook intake | Immediately rejects that secret.                                      |
| Token header                     | Sender (Make / Zapier)  | `x-notionapps-webhook-token`.                                         |
| Condition on `payload.*`         | Step                    | Gate.                                                                 |
| Create/update record maps        | Step                    | `payload.field` → Notion.                                             |
| Notify channel Webhook           | Step                    | Fire-and-forget outbound.                                             |
| Call webhook and wait            | Step                    | Sync POST; waits for JSON Webhook response.                           |
| Messaging inbound / subscriber   | Messaging               | Separate product; needs External API level. Uses the same app secret. |
| Idempotency key                  | Payload                 | Dedupes retries.                                                      |

#### What users see

Inbound and fire-and-forget Notify usually show nothing to the current tab. Call webhook and wait can Refresh / Open screen / Show message on the open tab when Live screen updates is on. Operators may also see a Work Queue item or Notification if you added those steps. An inbound sender sees only the HTTP status.

#### Limits and plans

* Messaging webhooks need **External API**. Workflow Notify webhooks and Call webhook and wait need Workflow on, not Messaging levels.
* Up to **five** active secrets per app. Revoke or expire one before generating another if you are at the cap.
* After the first active secret, the shared platform token no longer works for that app.
* Call webhook and wait needs a reachable HTTPS URL. Make must return JSON via **Webhook response**.
* Unpublished workflows: HTTP may return accepted with `runs_created: 0`.
* Payload size and rate limits follow the API. Keep test bodies small.
* Do not put secrets in Notion properties that guests can open.

#### Example

You generate a secret named `Make production` with a 90-day term. Make stores `x-notionapps-webhook-token` and POSTs `{ status, name, email }` to the app alias URL. Condition requires `approved`. Create record on Leads. Notify the outbound catcher. The sample lead appears in the app list; the catcher logs the outbound POST.

#### Fix problems

| Symptom                          | Likely cause                                                                | What to do                                                                                                 |
| -------------------------------- | --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| 401                              | Wrong, revoked, or expired secret, or a secret from another app             | Generate a new secret if you lost the value. Confirm the prefix matches this app. Stay on one environment. |
| 401 after you generated a secret | Sender still uses the old shared platform token                             | Update Make/Zapier to the new secret.                                                                      |
| Generate is blocked              | Inbound webhooks are disabled for the app                                   | Ask NotionApps to turn inbound back on.                                                                    |
| At the 5-secret cap              | Too many active secrets                                                     | Revoke or expire one, then generate.                                                                       |
| Accepted, 0 runs                 | Draft, or trigger not webhook\_received                                     | Publish. Check trigger.                                                                                    |
| Condition failed                 | `payload.status` mismatch                                                   | Log the raw body. Fix the path.                                                                            |
| Outbound missing                 | Notify not Webhook, or URL wrong                                            | Catcher URL. Publish.                                                                                      |
| Open tab stale after Make        | Used Notify, or Make has no Webhook response, or live screen updates is off | Use Call webhook and wait. End Make with Webhook response JSON. Ask NotionApps to turn the switch on.      |
| Messaging 403                    | Level below External API                                                    | Request access.                                                                                            |

#### Related

[Workflow vs Messaging Webhooks](https://docs.notionapps.com/automation/webhooks-and-integrations/workflow-vs-messaging-webhooks). [31 Aug 2026 Release](https://docs.notionapps.com/release-notes/31-aug-2026-release). [Trigger catalog](https://docs.notionapps.com/automation/advanced-reference/trigger-catalog). [Which automation](https://docs.notionapps.com/automation/which-automation).
