# Consuming webhook events
Source: https://www.inngest.com/docs/platform/webhooks

import {
  RiTerminalLine,
  RiGithubFill
} from "@remixicon/react";

At its core, Inngest is centered around functions that are triggered by events. Webhooks are one of the most ubiquitous sources for events for developers. Inngest was designed to support webhook events.

This guide will show you how to use Inngest to consume your webhook events and trigger functions.

<br />

When talking about webhooks, it's useful to define some terminology:

- **Provider** - The service sending the webhook events as HTTP post requests.
- **Consumer** - The URL endpoint which receives the HTTP post requests.

Inngest enables you to create any number of unique URLs which act as webhook consumers. You can create a webhook for each third party service that you use (e.g. Stripe, Github, Clerk) along with custom rules on how to handle that webhook.

## Creating a webhook

First, you'll need to head to the **Manage** tab in the Inngest dashboard and then click **Webhooks**. From there, click **Create Webhook**.

Now you'll have a uniquely generated URL that you can provide to any provider service to start sending events. These URLs are configured in different ways for different provider services. For example, with Stripe, you need to enter "developer mode" and configure your webhook URLs.

Give your webhook a name and save it. Next we'll explore how to turn the request payload into Inngest events.

![Inngest dashboard showing a newly created webhook](/assets/docs/platform/dashboard-webhook-screenshot.png)

## Defining a transform function

Most webhooks send event data as JSON within the POST request body. These raw events must be transformed slightly to be compatible with [the Inngest event payload format](/docs/features/events-triggers/event-format). Mainly, we must have `name` and `data` set in the Inngest event.

Fortunately, Inngest includes **_transform_** functions for every webhook. You can define a short JavaScript function used to transform the shape of the payload. This transform runs on Inngest's servers so there is no added load or cost to your infra.

Here is [an example of a raw webhook payload from Clerk](https://clerk.com/docs/integrations/webhooks/overview#payload-structure) on the left and our transformed event:

<Row>
  <Col>

```json {{ title: "Example Clerk webhook payload"}}
{
  "type": "user.created",
  "object": "event",
  "data": {
    "created_at": 1654012591514,
    "external_id": "567772",
    "first_name": "Example",
    "id": "user_29w83sxmDNGwOuEthce5gg56FcC",
    "last_name": "Example",
    "last_sign_in_at": 1654012591514,
    "object": "user",
    "primary_email_address_id": "idn_29w83yL7CwVlJXylYLxcslromF1",
    // ... simplified for example
  },
}
```

  </Col>
  <Col>

```json {{ title: "Example Inngest event format"}}
{
  "name": "clerk/user.created",
  "data": {
    "created_at": 1654012591514,
    "external_id": "567772",
    "first_name": "Example",
    "id": "user_29w83sxmDNGwOuEthce5gg56FcC",
    "last_name": "Example",
    "last_sign_in_at": 1654012591514,
    "object": "user",
    "primary_email_address_id": "idn_29w83yL7CwVlJXylYLxcslromF1",
    // ... simplified for example
  }
}
```

  </Col>
</Row>

Transforms are defined as simple JavaScript functions that accept three arguments and expect the Inngest event payload object in the returned value. The arguments are:

<Properties>
  <Property name="evt" type="object" attribute>
    The raw JSON payload from the POST request body
  </Property>
  <Property name="headers" type="object" attribute>
    A map of HTTP headers sent along with the request as key-value pairs. Header names are case-insensitive and are canonicalized by making the first character and any characters following a hyphen uppercase and the rest lowercase. For more details, [check out](https://pkg.go.dev/net/http#CanonicalHeaderKey) the underlying implementation reference.
  </Property>
  <Property name="queryParams" type="object" attribute>
    A map of parsed query string parameters sent to the webhook URL. Values are all arrays to support multiple params for a single key.
  </Property>
</Properties>

Here's a simple transform function for the Clerk example shown above:

```ts
function transform(evt, headers = {}, queryParams = {}) {
  return {
    name: `clerk/${evt.type}`,
    data: evt.data,
    // You can optionally set ts using data from the raw json payload
    // to explicitly set the timestamp of the incoming event.
    // If ts is not set, it will be automatically set to the time the request is received.
  }
}
```

<Callout>
  👉 We also recommend prefixing each event name with the name of the provider service, e.g. `clerk/user.created`, `stripe/charge.failed`.
</Callout>

### Example transforms

<Callout variant="info">
  💡 Header names are case-insensitive and are canonicalized by making the first character and any characters following a hyphen uppercase and the rest lowercase.

  Remember to check your transforms for header usage and make sure to use the correct case.
</Callout>

<details>
  <summary>**Github** - Using headers</summary>

  Github uses a `X-Github-Event` header to specify the event type:

```js
function transform(evt, headers = {}, queryParams = {}) {
  headers["X-Github-Event"];
  return {
    // Use the event as the data without modification
    data: evt,
    // Add an event name, prefixed with "github." based off of the X-Github-Event data
    name: "github." + name.trim().replace("Event", "").toLowerCase(),
  };
}
```
</details>

<details>
  <summary>**Stripe** - Using an `id` for deduplication</summary>

  Stripe sends an `id` with every event to deduplicate events. We can use this as the `id` for the Inngest event for the same reason:

```js
function transform(evt, headers = {}, queryParams = {}) {
  return {
    id: evt.id,
    name: `stripe/${evt.type}`,
    data: evt,
  };
}
```
</details>

<details>
  <summary>**Linear** - Creating useful event names</summary>

```js
function transform(evt, headers = {}, queryParams = {}) {
  return {
    // type (e.g. Issue) + action (e.g. create)
    name: `linear/${evt.type.toLowerCase()}.${evt.action}`,
    data: evt,
  };
}
```
</details>

<details>
  <summary>**Intercom** - Setting the `ts` field</summary>

```js
function transform(evt, headers = {}, queryParams = {}) {
  return {
    name: `intercom/${evt.topic}`,
    // the top level obj only contains webhook data, so we omit that
    data: evt.data,
    ts: evt.created_at * 1000,
   };
};
```
</details>

<details>
  <summary>**Resend**</summary>

```js
function transform(evt, headers = {}, queryParams = {}) {
  return {
    name: `resend/${evt.type}`,
    data: evt.data,
   };
};
```
</details>

### Testing transforms

The Inngest dashboard includes a tool to quickly test your transform function. You can paste the incoming payload from the webhook provider in the "Incoming Event JSON" editor and immediately preview what the transformed event will look like.

![Inngest dashboard transform testing](/assets/docs/platform/dashboard-webhook-transform-testing.png)

Some webhook providers do not provide example payloads in their documentation. If that's the case, you can use a tool that we built, [TypedWebhooks.tools](https://typedwebhook.tools/?ref=) to test webhooks and browse payloads.

## Advanced configuration

Additionally, you can configure allow/deny lists for event names and IP addresses. This can be useful if you want a bit more control over what events are ingested.

## Managing webhooks via REST API

Webhooks can be created, updated and deleted all via the Inngest REST API. This is very useful if you want to manage all transforms within your codebase and sync them to the Inngest platform. Check out the documentation below to learn more:

<CardGroup>
  <Card
    title="API: Webhooks"
    icon={<RiTerminalLine className="text-basis h-4 w-4"/>}
    href={'https://api-docs.inngest.com/docs/inngest-api/b539bae406d1f-get-all-webhook-endpoints-in-given-environment'}>
    Read the documentation about managing Webhooks via the Inngest REST API
  </Card>
  <Card
    title="Demo: Webhook transform sync"
    icon={<RiGithubFill className="text-basis h-4 w-4"/>}
    href={'https://github.com/inngest/webhook-transform-sync'}>
    View an end-to-end example of how to test and sync Webhooks in your codebase.
  </Card>
</CardGroup>

## Local development

To test your webhook locally, you can forward events to the [Dev Server](/docs/local-development) from the Inngest dashboard using the "Send to Dev Server" button. This button is found anywhere that an event payload is visible on the Inngest dashboard. This will send a copy of the event to your local machine where you can test your functions.

[IMAGE]

## Writing functions

Now that you have events flowing into Inngest, you can write functions that that handle the events that you care about. You can also explore the list of events that have been received at any time by heading to the _Events_ tab of the Inngest dashboard.

```ts {{ title: "Example: Send a welcome email when the clerk/user.created event is received"}}
inngest.createFunction(
  { name: "Send welcome email", id: "send-welcome-email" },
  { event: "clerk/user.created" },
  async ({ event, step }) => {
    event.data.email_addresses[0].email_address;
    await step.run('send-email', async () => {
      return await resend.emails.send({
        to: emailAddress,
        from: "noreply@inngest.com",
        subject: "Welcome to Inngest!",
        react: WelcomeEmail(),
      })
    });
  }
)
```

<Callout>
  💡 **Tip**: To test functions locally, copy an event from a webhook from the Inngest dashboard and use it with the Inngest dev server's `Send test` button.
</Callout>

## Verifying request signatures

Many webhook providers sign their requests with a secret key to ensure that the request is coming from them. This establishes trust with the webhook provider and ensures that the event data has not been tampered with.

To verify a webhook signature, you'll need to return the signature and raw request body string in your transform. For example, the following transform function could be used for Stripe webhooks:

```ts
function transform(evt, headers, queryParams, raw) {
  return {
    name: `stripe/${evt.type}`,
    data: {
      raw,
      sig: headers["Stripe-Signature"],
    }
  };
};
```

Then you can use that data to verify the signature in your Inngest functions:

```ts
inngest.createFunction(
  { id: "stripe/charge.updated" },
  { event: "stripe/charge.updated" },
  async ({ attempt, event, step }) => {
    if (!verifySig(event.data.raw, event.data.sig, stripeSecret)) {
      throw new NonRetriableError("failed signature verification");
    }

    // Now it's safe to use the event data.
    JSON.parse(event.data.raw);
  }
);
```

## Branch environments

All branch environments share the same webhooks. They are centrally-managed in a [single page](https://app.inngest.com/env/branch/manage/webhooks).

Additionally, the target branch environment must be specified using either an `x-inngest-env` query param or header. For example, the following command will send an webhook to the `branch-1` branch environment:

```sh
curl 'https://inn.gs/e/REDACTED?x-inngest-env=branch-1' -d '{"msg": "hi"}'
```

If the branch environment is not specified with the header or query param, the webhook will be sent to [this page](https://app.inngest.com/env/branch/events) and will not trigger any functions. Events will also go here if the branch environment does not exist.

<Callout>
  The value for `x-inngest-env` is the name of the branch environment, not the ID in the URL.
</Callout>

## Supported content types

Webhooks currently support the following content types:

- `application/json`
- `application/x-www-form-urlencoded`
- `multipart/form-data`

<Callout>
  The `application/x-www-form-urlencoded` and `multipart/form-data` content types are currently in beta.
</Callout>

### Processing webhooks with URL params or Form Data content

Given the following `application/x-www-form-urlencoded` webhook:

```sh
curl https://inn.gs/e/REDACTED \
  -H "content-type: application/x-www-form-urlencoded" \
  -d "name=Alice&messages=hello&messages=world"
```

And your webhook transform looks like this:
```js
function transform(json, headers, queryParams, raw) {
  return {
    name: "hi",
    data: { json, raw },
  };
};
```

Then the resulting event data will be:
```json
{
  "json": {
    "messages": ["hello", "world"],
    "name": ["Alice"]
  },
  "raw": "name=Alice&messages=hello&messages=world"
}
```

<Callout variant="warning">
  <span className="text-yellow-300">⚠️</span> **Note that the JSON object's values are always arrays of strings.**
</Callout>

## Building webhook integrations

Inngest has a feature called **webhook intents** that allows you to build webhook integrations with any application. [Read more about building webhook integrations here](/docs/platform/webhooks/build-an-integration).