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

# Webhooks

> Receive inbound webhooks from external services by adding a webhook trigger to a workflow.

Add a `webhook` trigger to a [workflow](/framework/workflows) and it runs whenever an external service
(Stripe, GitHub, a form provider) POSTs to its ingress URL.

<Note>
  These are **inbound** webhooks handled by your app's code. For **outbound** webhooks that notify your
  systems when database records change, see the [Database API webhooks](/api/webhooks/webhooks).
</Note>

## Defining a webhook workflow

```typescript theme={null}
// apps/helpdesk/src/api/stripeEvents.ts
import { z } from 'zod';
import { createEndpoint } from 'zitejs/backend';

export default createEndpoint({
  description: 'Handle Stripe webhook events',
  webhook: {},                      // marks this workflow as webhook-triggered
  inputSchema: z.any(),
  outputSchema: z.object({ received: z.boolean() }),
  execute: async ({ input }) => {
    // input is the raw parsed body; verify the provider signature here
    return { received: true };
  },
});
```

## The ingress URL

The workflow receives events at:

```text theme={null}
https://<runtime>/public/{flowId}/webhooks/{endpointId}/{token}
```

| Segment        | Value                                        |
| -------------- | -------------------------------------------- |
| `{flowId}`     | The app's public identifier                  |
| `{endpointId}` | The workflow's id, as declared in `src/api/` |
| `{token}`      | The secret that authenticates the request    |

<Warning>
  **The secret token in the path is the only authentication.** There is no framework-level HMAC — the token
  is what proves the request is legitimate at the transport level. Treat the full URL as a credential, and
  verify provider signatures yourself (see below) when the provider supports them.
</Warning>

The token is provided as an environment variable named `ZITE_WEBHOOK_TOKEN_<ENDPOINT>`, where `<ENDPOINT>`
is the workflow id upper-snake-cased — workflow `stripeEvents` becomes
`ZITE_WEBHOOK_TOKEN_STRIPE_EVENTS`.

## Verifying provider signatures

The request body is delivered **raw** so you can run provider-native signature verification on the exact
bytes — for example, Stripe's `constructEvent`:

```typescript theme={null}
import Stripe from 'stripe';

const stripe = new Stripe(process.env.ZITE_STRIPE_ACCESS_TOKEN);
const event = stripe.webhooks.constructEvent(rawBody, signatureHeader, process.env.ZITE_STRIPE_WEBHOOK_SECRET);
```

<Note>
  Inbound webhook bodies are limited to **1 MB**.
</Note>
