---
title: Webhooks Overview
---

Saleor can send real-time notifications or data to another application or service using Webhooks.

Webhooks are available in Saleor to both Local and External Apps. Local App is an entity tightly integrated into Saleor whereas External App is an externally hosted application that can communicate with Saleor API and integrate into Saleor Dashboard.

If your external system or service needs to receive data based on events happening in Saleor you can use Webhooks through Local Apps. To do that you need to create Local App which can be used to define Webhooks.

## Webhook creation
To create a Webhook you need to create a Local App first. You can do that in the Saleor Dashboard 
by navigating to the **Extentions** section, clicking **Add Extention** -> **Provide details manually**. 

When a Local App is created you can add a new Webhook by clicking **Create Webhook** button.
Webhooks can be created using the Saleor Dashboard, Saleor GraphQL API or Saleor CLI. All of these methods trigger [`webhookCreate`](../../../api-reference/webhooks/mutations/webhook-create.mdx) mutation.

Check [Creating Webhook](./creating.mdx) page for more details.

## Webhook payload

You can define the payload that will be sent to the Webhook. Saleor uses GraphQL subscription syntax to define the payload.

To define a payload you need to provide a valid GraphQL subscription query. Here is an example that would enable listening to the `PRODUCT_UPDATED` event:

```graphql
subscription {
  event {
    ... on ProductUpdated {
      product {
        id
        name
      }
    }
  }
}
```

The payload is sent in the `data` field of the subscription response. The payload is a JSON object with the following structure:

```json
{
  "event": "PRODUCT_UPDATED",
  "data": {
    "object": {
      "id": "ID",
      "name": "NAME"
    }
  }
}
```

You can read more about GraphQL subscriptions in the [Subscription Webhook Payloads](./subscription-webhook-payloads.mdx) section.

## Webhook headers

In addition to the payload several headers are included:

- `Saleor-Event` - defines an event that is assigned to the webhook
- `Saleor-Domain` - defines a Saleor domain
- `Saleor-Signature` - defines a signature to indicate that the request is verifiable
- `Saleor-Api-Url` - defines full Saleor GraphQL endpoint

:::warning
In Saleor 4.0 all `X-Saleor-` headers will be removed and replaced with headers without `X-` prefix.

Deprecated headers list:

- `X-Saleor-Event` -> `Saleor-Event`
- `X-Saleor-Domain` -> `Saleor-Domain`
- `X-Saleor-Signature` -> `Saleor-Signature`

:::

Saleor gives you the option to add custom headers to the request. `Authorization*` and `X-*` keys are allowed, with limitations of 5 headers per webhook and 998 characters per header. To check how to add `customHeaders` go to [Creating webhook](./creating.mdx).

## Payload signature

Webhook payload signature refers to a mechanism for verifying the authenticity and integrity of webhook payloads received from Saleor. When Saleor sends webhook notifications to a specified Target URL (endpoint), it includes a signature along with the payload. The webhook payload signature allows the recipient to verify that the payload originated from Saleor and that it hasn't been tampered with during transit.

Saleor uses the [HMAC](https://en.wikipedia.org/wiki/HMAC) algorithm to calculate the signature for each webhook payload. The HMAC algorithm requires a secret key as an input. The secret key is used to generate the signature and is known only to Saleor and the recipient of the webhook payload. The recipient uses the secret key to verify the signature and confirm that the payload originated from Saleor and that it hasn't been tampered with during transit.

You can read more about payload signature in the [Payload signature](/docs/developer/extending/webhooks/payload-signature.mdx) section.

## Events

Saleor can send Webhooks for various events that happen in the system. Events can be divided into two categories: **synchronous** and **asynchronous**. Synchronous events are sent immediately after the event happens during GraphQL requests. Asynchronous events are sent after request processing is finished.

The main and **most important difference** between synchronous and asynchronous events is that **synchronous events execution influences the response time of the request that triggered the event**. That said if you have a request that triggers a synchronous event and the event processing takes a long time, the request will take a long time to finish as well affecting the response time of the request.

You can read more about asynchronous events in the [Asynchronous events](./asynchronous-events.mdx) section and about synchronous events in the [Synchronous events](./synchronous-events/overview.mdx) section. 
For more information about common webhook events issues and how to resolve them, check out the [Troubleshooting](./troubleshooting.mdx) section.

## Time limits

Both synchronous and asynchronous webhook requests are time-limited. Saleor will wait a maximum of 20 seconds for the complete HTTP request round trip: up to 2 seconds for the network connection and up to 18 seconds for a response.

Some operations can result in more than one synchronous webhook invocation. While individual calls can take up to 20 seconds and succeed, stacking them up may cause Saleor's API to time out.

You can follow some good practices to avoid webhook timeouts:

- Use queues instead of HTTP endpoints for asynchronous events. Saleor will send events straight to the queue, and your application can process them at its own pace without blocking Saleor.
- If you're using serverless functions to accept webhooks, be aware of cold starts. Make sure the application is fast to respond even if it was not invoked in a while.
- If you use Serverless environment consider migrating to Edge runtime (e.g. Cloudflare Workers, Vercel Edge Runtime, Deno Deploy) which has much lower cold starts (close to 0)
- The 20-second limit is there to act as a fail-safe mechanism. We recommend ensuring your application never gets close to that limit, even under heavy load. For some events, even a one-second delay can result in a poor user experience.

## Webhook Dry Run

Saleor provides a [`webhookDryRun`](/api-reference/webhooks/mutations/webhook-dry-run.mdx) mutation 
that lets you test what a webhook payload would look like, without actually sending it to the target URL. 
This is useful for debugging subscription queries, ensuring your payload format is correct, and testing integrations safely.

:::warning
The dry run feature works only for **asynchronous webhooks**. For details, see [dry run limitations](#dry-run-limitations)
:::

Mutation example: 

```graphql
mutation TriggerWebhookDryRun($objectId: ID!, $query: String!) {
  webhookDryRun(objectId: $objectId, query: $query) {
    payload
    errors {
      field
      message
    }
  }
}
``` 

Variables:
```json
{
  "objectId": "UHJvZHVjdDoxNTI=",
  "query": "subscription {\n  event {\n    ... on ProductUpdated {\n      __typename\n      product{\n        id\n        name\n        description\n      }\n    }\n  }\n}\n"
}
```

Response:
```json
{
  "data": {
    "webhookDryRun": {
      "payload": "{\"__typename\": \"ProductUpdated\", \"product\": {\"id\": \"UHJvZHVjdDoxNTI=\", \"name\": \"Apple Juice\", \"description\": \"{\\\"time\\\": 1653425438149, \\\"blocks\\\": [{\\\"id\\\": \\\"rGR983yNVl\\\", \\\"data\\\": {\\\"text\\\": \\\"<b>Fell straight from the tree</b>, on to Newton\\\\u2019s head, then into the bottle. The autumn taste of English apples. Brought to you by gravity.\\\"}, \\\"type\\\": \\\"paragraph\\\"}], \\\"version\\\": \\\"2.22.2\\\"}\"}}",
      "errors": [],
      "__typename": "WebhookDryRun"
    }
  }
}
```
The `webhookDryRun` mutation requires the same permissions as the event being tested.

Example: If the subscription queried OrderCreated, the user would need `MANAGE_ORDERS`.


### Using Dry Run from the Dashboard

You don’t need to run the mutation manually to benefit from dry runs.

From the Saleor Dashboard, you can:

1. Navigate to your app select a webhook.
2. In the subscription payload editor, click the play button ▶️.
3. You’ll be prompted to choose an object (e.g., a product, order, or other supported type).

Saleor will run the dry run query for that object and show you the generated payload directly in the dashboard.

### Dry Run Limitations
Not all events can be triggered using dry run.

**Not supported in dry run:**
- **Synchronous events**: tax calculation, external/filtering shipping methods
- **Transactions**: captures, refunds, authorization, processing, session initialization
- **Stored payment methods**: tokenization or deletion requests
- **User account management events**: confirmation, password change
- **Fulfillments**: creation, approvals
- **Gift Card Sending**: sending to customers
- **Thumbnail Creation**: media-related events
- **Order Bulk Imports**: batch events