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

# Sources and verification

> Choose an endpoint's source, configure verification, and type the event its subscribers receive.

export const PrivateBeta = ({children}) => <Warning>
    <strong>Private beta.</strong> This feature is only available to some organizations right now.
    {children}
  </Warning>;

<PrivateBeta flag="hasWebhooksAccess">
  [Request access](https://trigger.dev/webhooks-beta) and we'll turn webhooks on for your
  organization and send you the prerelease packages. Webhooks are open source, so they work
  self-hosted too. APIs may change before general availability.
</PrivateBeta>

An endpoint's `source` defines how Trigger.dev verifies requests and types the event every subscriber receives. Choose a named provider source or supply a verification configuration with `webhooks.custom()`.

## Presets

Presets supply the verification configuration, and most also carry the provider's part of the endpoint's [setup prompt](/docs/webhooks/connect#set-it-up-with-an-ai-agent). Set the endpoint's signing secret or public key as described in [Connect a provider](/docs/webhooks/connect).

<CodeGroup>
  ```ts /trigger/stripe.ts theme={"theme":"css-variables"}
  import { webhook, webhooks } from "@trigger.dev/sdk";

  export const stripe = webhooks.endpoint.define({ id: "stripe", source: webhooks.stripe() });

  export const stripeEvents = webhook({
    id: "stripe-events",
    endpoint: stripe,
    onEvent: async ({ event }) => {
      if (event.type === "payment_intent.succeeded") {
        console.log(event.id);
      }
    },
  });
  ```

  ```ts /trigger/github.ts theme={"theme":"css-variables"}
  import { webhook, webhooks } from "@trigger.dev/sdk";

  export const github = webhooks.endpoint.define({ id: "github", source: webhooks.github() });

  export const githubEvents = webhook({
    id: "github-events",
    endpoint: github,
    onEvent: async ({ headers }) => {
      // GitHub puts the event type in a header
      console.log(headers.get("x-github-event"));
    },
  });
  ```

  ```ts /trigger/svix.ts theme={"theme":"css-variables"}
  import { webhook, webhooks } from "@trigger.dev/sdk";

  export const svix = webhooks.endpoint.define({ id: "svix", source: webhooks.svix() });

  export const svixEvents = webhook({
    id: "svix-events",
    endpoint: svix,
    onEvent: async ({ event }) => {
      console.log(event);
    },
  });
  ```

  ```ts /trigger/composio.ts theme={"theme":"css-variables"}
  import { webhook, webhooks } from "@trigger.dev/sdk";

  export const composio = webhooks.endpoint.define({ id: "composio", source: webhooks.standardWebhooks() });

  export const composioEvents = webhook({
    id: "composio-events",
    endpoint: composio,
    onEvent: async ({ event }) => {
      console.log(event);
    },
  });
  ```
</CodeGroup>

The available presets are `stripe()`, `github()`, `svix()`, `standardWebhooks()`, `square()`, and `discord()`.

### Svix and Standard Webhooks

`svix()` and `standardWebhooks()` use HMAC-SHA256 over `id.timestamp.body` with a base64-encoded `whsec_` secret. Their header names differ:

| Source | Headers |
| - | - |
| `svix()` | `svix-id`, `svix-timestamp`, `svix-signature` |
| `standardWebhooks()` | `webhook-id`, `webhook-timestamp`, `webhook-signature` |

Named sources such as `webhooks.clerk()` and `webhooks.composio()` select the corresponding configuration.

## Named providers

Each function below returns a source with its provider's verification configuration. Call it through `webhooks`, for example `webhooks.calCom()`.

| Verification | Source functions |
| - | - |
| Svix | `clerk()`, `resend()`, `brex()` |
| Standard Webhooks | `openai()`, `replicate()`, `recallai()`, `composio()` |
| HMAC-SHA256 with `X-Hub-Signature-256` | `whatsapp()` |
| Shared secret in `X-Gitlab-Token` | `gitlab()` |
| Provider-specific HMAC | `calendly()`, `calCom()`, `sentry()`, `pagerduty()`, `vercel()`, `linear()`, `notion()`, `typeform()`, `docusign()`, `jira()`, `intercom()`, `zendesk()`, `attio()`, `workos()`, `elevenlabs()`, `vapi()`, `retell()`, `shopify()` |

Provider IDs containing a hyphen use a camel case function name: the `cal-com` provider is `webhooks.calCom()`.

Slack also ships as a chat channel in `@trigger.dev/slack`.

## Endpoint validation

Some sources also define a response for the provider's endpoint-validation request. These requests don't create deliveries or task runs.

`discord()` answers a verified `{ type: 0 }` PING with HTTP `204` and no body. It also returns `204` for accepted events and `401` for invalid signatures. Set the Discord application's public key as the endpoint's verification credential.

Slack's `url_verification` challenge is handled the same way by `@trigger.dev/slack`.

`whatsapp()` answers Meta's GET verification request when `hub.mode` is `subscribe` and `hub.verify_token` matches the endpoint's verify token. It returns `hub.challenge` as text. The verify token is separate from the App Secret used to verify event signatures; see [Verify a WhatsApp endpoint URL](/docs/webhooks/connect#verify-a-whatsapp-endpoint-url). Endpoints without GET verification return `405` to GET requests.

## Custom providers

`webhooks.custom()` accepts a verification configuration. This source verifies an HMAC-SHA256 signature over the raw body in `x-webhook-signature` and uses the event's `id` for deduplication:

```ts /trigger/internal.ts theme={"theme":"css-variables"}
import { webhook, webhooks } from "@trigger.dev/sdk";

export const internal = webhooks.endpoint.define({
  id: "internal",
  source: webhooks.custom<{ id: string; message: string }>(
    {
      scheme: "hmac",
      algorithm: "sha256",
      encoding: "hex",
      signatureHeader: "x-webhook-signature",
      signingString: "raw",
      idempotencyField: { from: "body", name: "id" },
    },
    { setupPrompt: "Add the URL under Settings > Webhooks in the admin app and paste the secret there." }
  ),
});

export const internalEvents = webhook({
  id: "internal-events",
  endpoint: internal,
  onEvent: async ({ event }) => {
    console.log(event.message);
  },
});
```

The optional `setupPrompt` is the provider-specific part of the endpoint's [setup prompt](/docs/webhooks/connect#set-it-up-with-an-ai-agent): where the provider's webhook settings are and how to register a URL there. Trigger.dev adds the URL, the subscribers' filters and the signing secret steps around it.

### Form-encoded bodies

A verified body sent as `application/x-www-form-urlencoded` reaches subscribers as an object of its fields, so filters, `chat.event` keys and waiter `match` paths read form fields the way they read JSON fields. A repeated field becomes an array of its values. Slack slash commands and other providers that post forms work this way.

By default an accepted request gets a `200` with a small JSON receipt. Some providers show the response body to the user who triggered the request, so pass `response: { acceptedBody: "empty" }` to answer with an empty `200` instead:

```ts /trigger/forms.ts theme={"theme":"css-variables"}
import { webhook, webhooks } from "@trigger.dev/sdk";

export const forms = webhooks.endpoint.define({
  id: "forms",
  source: webhooks.custom<{ command: string; text: string; user_id: string }>(
    { scheme: "url-secret", placement: "query", paramName: "token" },
    { response: { acceptedBody: "empty" } }
  ),
});

export const commands = webhook({
  id: "commands",
  endpoint: forms,
  filter: "event.command == '/deploy'",
  onEvent: async ({ event }) => {
    console.log(event.user_id, event.text);
  },
});
```

## Typing the event

Stripe and GitHub sources provide default event shapes; other sources default to `unknown`. Pass an event type to the source function for typed handler parameters and autocomplete. This example uses `Stripe.Event` from the `stripe` package, which must be installed in your project:

```ts /trigger/stripe.ts theme={"theme":"css-variables"}
import type Stripe from "stripe";
import { webhook, webhooks } from "@trigger.dev/sdk";

export const stripe = webhooks.endpoint.define({ id: "stripe", source: webhooks.stripe<Stripe.Event>() });

export const stripeEvents = webhook({
  id: "stripe-events",
  endpoint: stripe,
  onEvent: async ({ event }) => {
    console.log(event.type, event.id);
  },
});
```

For `webhooks.custom<T>()`, pass your own event type as `T`. The endpoint's event type flows to every subscriber: `webhook()` handlers, `chat.event` keys and filters, and [waiter](/docs/webhooks/waiters) `match` paths are all checked against it.

<Note>
  Event types are compile-time annotations. Signature verification doesn't validate the body against
  a TypeScript type. Validate `event` inside `onEvent` if your handler needs runtime schema checks.
</Note>

## Verification schemes

Requests must pass verification before they reach any subscriber. Sources can check a signature or credential, enforce a timestamp tolerance, and derive an idempotency key for [deduplication](/docs/webhooks/deliveries#deduplication). Verification failures return the source's rejected HTTP status (`400` by default).

Custom sources support these schemes:

| Scheme | Verification |
| - | - |
| `hmac` | Computes an HMAC over the raw body or a signing template and compares it with a request header. |
| `shared-secret` | Compares a static token supplied in a header, bearer token, basic authentication, or request body. |
| `url-secret` | Compares a token in a URL query parameter. Hosted endpoints don't support path placement. |
| `asymmetric` | Verifies an Ed25519, ECDSA, or RSA signature using the provider's public key. |

<Warning>
  Query parameters can appear in access logs, proxies, and traces. Prefer a header-based scheme when
  the provider supports one.
</Warning>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.