# Webhooks

> Learn how webhook actions send event payloads to an external URL when a trigger fires.

You can configure a trigger to send an HTTP request to an external URL when an event is fired. This is called a webhook action. Use webhooks to integrate UGS events with your own backend, third-party APIs, or notification services (for example Slack or Discord) without running Cloud Code.

When the trigger fires, the Triggers service sends an HTTP request to the URL you configure, using the method, headers, and body you specify. Use a payload template to shape the request body from the event data. Failed deliveries are stored in the [dead letter queue](/triggers/tutorials/manage-dlq/dead-letter-queue.md), where you can inspect and replay them.

## When to use webhooks

Use a webhook action when you want to:

* Notify an external system when a UGS event occurs (for example, player signed up, score submitted, or a scheduled time).
* Forward event data to your own server or a third-party API in a custom format.
* Post notifications to a messaging service (for example Slack or Discord) when players perform key actions.
* Avoid running Cloud Code for simple HTTP callouts.

Use a [Cloud Code](./tutorials/define-triggers/trigger-structure) action when you need to run game logic, call other UGS APIs with a service token, or perform multi-step logic before calling an external URL.

## Webhook configuration

When you create a trigger with `actionType: webhook`, you must provide a `webhook` object with the following fields:

| Field             | Required    | Description                                                                                                                                                                                                                                                |
| ----------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `url`             | Yes         | The destination URL. Must be a valid HTTP or HTTPS endpoint that resolves to a public IP address. Maximum length 2,048 characters.                                                                                                                         |
| `method`          | No          | The HTTP method. Supported values: `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `HEAD`, `OPTIONS`. Defaults to `POST` when omitted.                                                                                                                            |
| `headers`         | No          | HTTP headers as key-value pairs. Maximum 10 entries. Each header name must be ASCII and at most 256 characters. Each header value must not be empty and must be at most 8,192 characters. If `Content-Type` is not set, it defaults to `application/json`. |
| `payloadTemplate` | Conditional | A template that builds the request body from the event payload. Required when `Content-Type` is not `application/json`. Optional for JSON — when omitted, the full event payload is forwarded as-is. Maximum 100,000 characters.                           |

The trigger also requires an `actionUrn` that identifies the webhook. Use `urn:ugs:webhook`. The URN is used for logging and [dead letter queue](/triggers/tutorials/manage-dlq/dead-letter-queue.md) management when delivery fails.

> **Note:**
>
> The webhook URL must resolve to a public IP address. The Triggers service rejects URLs that point to localhost, private IP ranges, or link-local addresses at the time the trigger is created.

### Content types

The following content types are supported for webhook payloads. Set the content type by including a `Content-Type` header in the `headers` field, or by selecting it in the Unity Dashboard.

| Content type                        | Payload format                                                                                              |
| ----------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `application/json` (default)        | Valid JSON. Payload is optional — when omitted, the full event payload is forwarded as-is.                  |
| `application/xml`                   | XML body.                                                                                                   |
| `text/plain`                        | Raw text body.                                                                                              |
| `text/html`                         | HTML body.                                                                                                  |
| `application/x-www-form-urlencoded` | URL-encoded key-value pairs separated by `&` (for example `a=1&b=2`, with keys and values percent-encoded). |
| `multipart/form-data`               | Multipart body with boundary lines and `Content-Disposition: form-data` headers for each field.             |

For all content types other than `application/json`, the `payloadTemplate` field is required.

### Auto-injected headers

The Triggers service automatically adds the following headers to every outbound webhook request. You cannot override the `User-Agent` header. The `Content-Type` and `Accept` headers are only added if you have not already set them in your trigger configuration.

| Header             | Value                | Notes                                                           |
| ------------------ | -------------------- | --------------------------------------------------------------- |
| `Content-Type`     | `application/json`   | Added as default only if not already present in your headers.   |
| `Accept`           | `application/json`   | Added as default only if not already present in your headers.   |
| `User-Agent`       | `Unity-Triggers/1.0` | Always set; cannot be overridden.                               |
| `X-Request-ID`     | Request identifier   | A UUID set per-request for tracing purposes.                    |
| `X-Unity-Event-ID` | Event ID             | The unique ID of the event that fired the trigger.              |
| `traceparent`      | W3C trace context    | Included to support distributed tracing on your end, if needed. |

### Template syntax

URL, headers, and `payloadTemplate` support template expressions so you can inject event data and secrets. Wrap expressions in double curly braces `{{ }}`.

Templates are evaluated at request time. If a referenced event field does not exist, it renders as an empty string rather than causing an error.

#### Event payload

In all three (URL, headers, `payloadTemplate`), the current event payload is available as the root object. Use `{{.}}` to output the entire payload as JSON, or `{{.fieldName}}` to output a single field (for example `{{.playerId}}`, `{{.score}}`). Field names match the event payload structure for the given event type. Refer to [Supported UGS events](./manage-events/supported-ugs-events) for payload shapes per event type.

> **Note:**
>
> Template field access (for example `{{.playerId}}`) requires the event payload to be a JSON object. The payload from all UGS events is a JSON object, so this is satisfied automatically for standard event triggers.

#### Event fields in headers

You can use event payload fields in header values, for example `X-Player-ID: {{.playerId}}` or `X-Score: {{.score}}`.

### Using secrets in templates

You can reference secrets stored in [Secret Manager](/services/secret-manager.md) inside templates to handle authentication, signature verification, or to inject other sensitive values. The Triggers service resolves secrets at request time, so the raw value is never stored in the trigger configuration.

Store each secret in Secret Manager and use its key as `SECRET_NAME` in the template expression. The following functions are available:

| Function                          | Description                                                                                                                                                                                                                                                                          | Example                                               |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------- |
| `{{ hmac_sha256 "SECRET_NAME" }}` | HMAC-SHA256 signature of the final request body, hex-encoded. Uses the named secret as the shared key. Your backend recomputes the signature using the same shared secret and compares it to verify the request came from Triggers. Computed after the payload template is rendered. | `X-Signature-256: {{ hmac_sha256 "WEBHOOK_SECRET" }}` |
| `{{ hmac_sha512 "SECRET_NAME" }}` | HMAC-SHA512 signature of the final request body, hex-encoded. Uses the named secret as the shared key. Your backend recomputes the signature using the same shared secret and compares it to verify the request came from Triggers. Computed after the payload template is rendered. | `X-Signature-512: {{ hmac_sha512 "WEBHOOK_SECRET" }}` |
| `{{ secret "SECRET_NAME" }}`      | Raw secret value. Use for API keys or Basic auth credentials.                                                                                                                                                                                                                        | `Authorization: Basic {{ secret "API_KEY" }}`         |
| `{{ jwt }}`                       | Short-lived JWT minted by the Triggers service for the request. Contains `projectId` and `environmentId` claims. Use for Bearer auth to confirm the request originates from your Triggers project.                                                                                   | `Authorization: Bearer {{ jwt }}`                     |

Your backend can verify an HMAC signature by independently computing the same HMAC over the received request body using the same shared secret, and comparing the result to the signature header value. The shared secret is stored in Secret Manager and is known only to the Triggers service and your backend.

To verify a JWT, your backend validates the token's signature and checks that the `projectId` and `environmentId` claims match the expected values for your project.

### Quoting and escaping template expressions

Template expressions use double quotes for function argument names:

```text
{{ secret "SECRET_NAME" }}
{{ hmac_sha256 "SECRET_NAME" }}
```

In the Unity Dashboard, enter template expressions exactly as shown above. The Dashboard handles JSON serialization automatically when it submits the form.

When configuring via the API or CLI, the template is sent as a JSON string value. Every `"` in the template,  including those inside template function argument names, must be escaped as `\"`. For example, a header value:

```json
"X-Signature-256": "{{ hmac_sha256 \"WEBHOOK_SECRET\" }}"
```

For example, a `payloadTemplate` with a secret inside a JSON string value:

```json
"payloadTemplate": "{\"token\": \"{{ secret \"VERIFY_TOKEN\" }}\"}"
```

### URL

Use template expressions to inject secrets or event data into the webhook URL.

| Expression                   | Description                                                | Example                                                 |
| ---------------------------- | ---------------------------------------------------------- | ------------------------------------------------------- |
| `{{ secret "SECRET_NAME" }}` | Insert the secret value into the URL path or query string. | `https://mywebsite.com/12345/{{ secret "PATH_TOKEN" }}` |
| `{{.fieldName}}`             | Insert an event payload field into the URL.                | `https://api.example.com/users/{{.playerId}}/events`    |

### Payload template

Use template expressions to build the request body from the event payload.

| Expression                   | Description                          | Example                                    |
| ---------------------------- | ------------------------------------ | ------------------------------------------ |
| `{{.}}`                      | Entire event payload as JSON.        | `{"wrapped": {{.}}}`                       |
| `{{.fieldName}}`             | Single field from the event payload. | `{"score": {{.score}}}`                    |
| `{{ secret "SECRET_NAME" }}` | Insert a secret value into the body. | `{"token": "{{ secret "VERIFY_TOKEN" }}"}` |

Wrap the event and add a shared secret for verification (API/CLI format):

```json
"payloadTemplate": "{\"event\": \"score-submitted\", \"data\": {{.}}, \"token\": \"{{ secret \"VERIFY_TOKEN\" }}\"}"
```

### Example webhook trigger

The following example shows how to create a webhook trigger using the API. It sends a POST request when a leaderboard score is submitted, with a JSON body built from the event and an HMAC signature header:

```json
{
  "name": "notify-score-webhook",
  "eventType": "com.unity.services.leaderboards.score-submitted.v1",
  "actionType": "webhook",
  "actionUrn": "urn:ugs:webhook",
  "webhook": {
    "url": "https://example.com/events",
    "method": "POST",
    "headers": {
      "Content-Type": "application/json",
      "X-Signature-256": "{{ hmac_sha256 \"WEBHOOK_SECRET\" }}"
    },
    "payloadTemplate": "{\"event\": \"score-submitted\", \"data\": {{.}}}"
  }
}
```

Ensure the secret `WEBHOOK_SECRET` exists in [Secret Manager](/services/secret-manager.md) and that the Triggers service has access to it.

### Filters

You can add a [filter](./tutorials/define-triggers/filters) to a webhook trigger so the HTTP request is only sent when the filter condition is true. For example, only send when a specific leaderboard is used or when a score is above a threshold.

## Retries and failed delivery

The Triggers service retries webhook delivery on server errors, timeouts, and rate-limiting responses. It makes up to 3 application-level retries using exponential backoff. After retries are exhausted, the event is stored in the [dead letter queue](/triggers/tutorials/manage-dlq/dead-letter-queue.md), where you can replay or discard it. Client errors (4xx, except 429) are not retried and are not added to the queue.

For the full retry behavior including which status codes are retried and how `Retry-After` is handled, refer to [Failure handling](failure-handling). For delivery rate limits and DLQ capacity, refer to [Limits](./limits#webhooks).

## Additional resources

* [Trigger structure](./tutorials/define-triggers/trigger-structure)
* [Dead letter queue](/triggers/tutorials/manage-dlq/dead-letter-queue.md)
* [Failure handling](failure-handling)
* [Define triggers in the Unity Dashboard](./tutorials/define-triggers/unity-dashboard)
* [Use case sample: Notify a Discord channel](./tutorials/use-cases/notify-discord-channel)
* [Secret Manager](/services/secret-manager.md)
