# Integrate webhooks

**URL:** https://heroiclabs.com/docs/satori/concepts/performance-monitoring/integrate-webhooks/
**Categories:** satori

---


# Integrate webhooks

The **Webhooks** tab lets you configure webhooks to receive real-time event data in your system or backend. Satori supports several event types, including live events, scheduled messages, feature flags, and feature flag variants.

Satori Webhooks are a powerful tool for automating workflows and integrating external services with your game operations. For example, you can listen for a webhook event in your backend and then update player statistics when a live event starts or when a message is sent to a user.

Webhooks are configured at a global level and apply to all Live Events, Messages, Feature Flags and Flag Variants.

## Creating webhooks
1. Navigate to the Webhooks tab and click "Create Webhook".

2. A modal will appear showing different webhook settings:

![Create Webhook]({{< fingerprint_image "/images/pages/satori/concepts/monitoring/monitoring_webhooks-create.png" >}})

The following overview explains each setting:
- **URL (required):** The endpoint on your server that listens for webhook events (e.g., `https://www.your-domain.com/satoriwebhook`). If pointing to a Nakama RPC endpoint, append `?unwrap=true` to the URL (e.g., `https://your-nakama-server.com/v2/rpc/my_function?http_key=defaulthttpkey&unwrap=true`). 
- **Description:** A freeform description to help you remember the purpose of this webhook.
- **Custom Header:** Optional HTTP header to include with each webhook request (for example, you might include a unique game ID if you're using the same webhook for multiple games).
    - **Name:** The custom header name (e.g., `game_id`).
    - **Value:** The value sent for this header (e.g., `game4`).
- **Listening for (required):** One or more Satori events that will trigger this webhook. You can find the list of all supported webhook events in the following table:

| Event | Description |
|---|---|
| `LIVE_EVENT_STARTED` | Triggered when a live event run starts. |
| `LIVE_EVENT_ENDED` | Triggered when a live event run ends. |
| `SCHEDULED_MESSAGE_SENT` | Triggered when a scheduled message is sent to players. |
| `FEATURE_FLAG_CREATED` | Triggered when a feature flag is created. |
| `FEATURE_FLAG_UPDATED` | Triggered when a feature flag is updated. |
| `FEATURE_FLAG_DELETED` | Triggered when a feature flag is deleted. |
| `FEATURE_FLAG_VARIANT_CREATED` | Triggered when a feature flag variant is created. |
| `FEATURE_FLAG_VARIANT_UPDATED` | Triggered when a feature flag variant is updated. |
| `FEATURE_FLAG_VARIANT_DELETED` | Triggered when a feature flag variant is deleted. |

3. After entering the required fields (URL and "Listening For") and any optional fields, click "Create." Your new webhook will appear under the Webhooks tab.

## Webhook details
To view and edit a specific webhook, select it under the Webhooks tab.  You'll be taken to its details page.

![Webhook Details]({{< fingerprint_image "/images/pages/satori/concepts/monitoring/monitoring_webhooks-details.png" >}})

The top of the page displays the webhook's details. Below, the "Events" section displays information about each HTTP request and response when you select a triggered event.

On this page, you can also:
- Send test events.
- Edit the webhook.
- Reset the signing secret (via the three-dot menu).
- Delete the webhook (via the three-dot menu).

### Auditing webhook requests
The "Events" section in the webhook details displays all previous attempts, including successful and failed deliveries. Click any attempt to view the request headers, request body, and response in the right panel.

![Webhook Attempts]({{< fingerprint_image "/images/pages/satori/concepts/monitoring/monitoring_webhooks-attempts.png" >}})

### Using the signing secret
To ensure the security and integrity of webhook payloads, Satori uses signatures. Each webhook has a signing secret that's used to generate a signature for each payload. Your server can verify this signature to confirm that the payload was sent by Satori and hasn't been tampered with.

Below is an example JavaScript snippet to verify the signature:

```javascript
import express from 'express';
import bodyParser from 'body-parser';

const SIGNING_SECRET = 'YOUR_SIGNING_SECRET';

const hexStringToUint8Array = hexString => {
  const bytes = new Uint8Array(Math.ceil(hexString.length / 2));
  for (let i = 0; i < bytes.length; i++) bytes[i] = parseInt(hexString.substr(i * 2, 2), 16);
  return bytes;
};

const verifySignature = async (body, header, tolerance = 300) => {
  if (!header) return false;

  header = header.split(',').reduce((accum, x) => {
    const [k, v] = x.split('=');
    return { ...accum, [k]: v };
  }, {});

  const encoder = new TextEncoder();
  const key = await crypto.subtle.importKey("raw", encoder.encode(SIGNING_SECRET), { name: "HMAC", hash: "SHA-256" }, false, ["verify"]);
  const verified = await crypto.subtle.verify("HMAC", key, hexStringToUint8Array(header.sig), encoder.encode(`${header.t}.${body}`));

  const elapsed = Math.floor(Date.now() / 1000) - Number(header.t);

  return verified && !(tolerance && elapsed > tolerance)
};

const app = express();

// Middleware to capture the raw request body
app.use(bodyParser.json({
  verify: (req, res, buf) => {
    req.rawBody = buf.toString();
  }
}));

// ...process the webhook payload
async function processWebhookPayload(payload) { }

app.use('/webhook', async (req, res) => {
  const verified = await verifySignature(req.rawBody, req.headers['x-satori-signature'])

  if (!verified) return res.status(403).send({ message: 'Invalid signature' });

  processWebhookPayload(req.body);

  res.status(200).send({ message: 'Valid signature' });
});

app.listen(8039, () => {
  console.log('Server running on http://localhost:8039');
});
```

If you suspect your signing secret has been compromised, select "Reset Secret" from the three-dot menu to generate a new one.

### Testing webhooks
To verify that your webhook works correctly, click "Send Test Event" to trigger a test payload. A modal will appear, allowing you to choose the event type and view the request contents.

<img src="{{< fingerprint_image "/images/pages/satori/concepts/monitoring/monitoring_webhooks-send-test-event.png" >}}" style="max-width: 70%; display: block; margin: 0 auto;">

When a test event is sent, it appears in the "Events" section with a `TEST` icon.

## Contents of webhook requests
Each webhook request body consists of three main parts:
- **id:** unique identifier for the request.
- **data:** information about the event triggered. Details provided for each event type in its relevant section.
- **event:** name of the Satori event the webhook triggered for.

### Live event started & ended
For "Live Event Started" and "Live Event Ended" events, the request body looks like this:

```json
{
  "id": "ec65eb00-4546-4004-8045-2e262651defd",
  "data": {
    "id": "57b0ef5d-fbfb-49f3-bb85-0bc20509bc22",
    "name": "test-event",
    "start_time_sec": 1728998280,
    "duration_sec": 360,
    "reset_cron": "* * * * *",
    "run_start": 1729854480,
    "run_end": 1729854510
  },
  "event": "LIVE_EVENT_STARTED"
}
```

Within the **data** object:
- **id:** The unique identifier for the live event.
- **name:** The live event's name.
- **start_time_sec:** The live event's start time as a UNIX timestamp (in seconds).
- **duration_sec:** The live event's duration (in seconds).
- **reset_cron:** The live event's reset cron expression.
- **run_start:** The start time of this run as a UNIX timestamp (in seconds).
- **run_end:** The end time of this run as a UNIX timestamp (in seconds).

### Scheduled message sent
For this event, the request body looks like:

```json
{
  "id": "9b8401bc-9c85-445c-94fd-06da9ead7e6e",
  "data": {
    "messages": [
      {
        "id": "39b1abac-59f7-4bcb-a815-22029142a0f1",
        "identity_id": "00000000-0000-0000-0000-000000000001",
        "title": "Attention!",
        "payload": "Jack, the world needs your help!",
        "image_url": "",
        "send_time_sec": 1736354260
      },
      {
        "id": "8a9f8033-8669-40da-acf4-ba7499ecdcd6",
        "identity_id": "00000000-0000-0000-0000-000000000003",
        "title": "Attention!",
        "payload": "Paul, the world needs your help!",
        "image_url": "",
        "send_time_sec": 1736354260
      }
    ],
    "message_schedule": {
      "id": "55e39305-bd09-4a89-877f-e7262fb2a506"
    },
    "live_event": {
      "id": "83e6e6d1-ad06-4ca3-8b0d-c1f01bbf935f",
      "value": "{}"
    }
  },
  "event": "SCHEDULED_MESSAGE_SENT"
}
```

Within the **data** object:
- **messages:** An array of messages, each containing:
  - **id:** The unique message instance identifier (UUID).
  - **identity_id:** The identity ID of the player receiving the message.
  - **title:** The message's title.
  - **payload:** The message's content.
  - **image_url:** The URL of the message's image, if any.
  - **send_time_sec:** The scheduled send time as a UNIX timestamp (in seconds).
- **message_schedule:** An object containing information about the message schedule, such as the schedule ID.
- **live_event:** An object containing information about the linked live event, including its ID and value.

### Feature flag webhooks
There are 3 type of webhook events related with Feature Flags:
- `FEATURE_FLAG_CREATED`
- `FEATURE_FLAG_UPDATED`
- `FEATURE_FLAG_DELETED`

For all three events, you'll receive similar information in your webhook. You can find an example of the request body below:

```json
{
  "id": "63f70120-1ed4-49dd-9c4e-85effb186ade",
  "data": {
    "id": "c29ab570-b9ba-47f9-ba6a-1e48762f34fd",
    "name": "Min-Build-Number",
    "variants": [
      {
        "id": "4fa27911-d5dd-4117-9676-057690cdc9d8",
        "name": "Early-Access-Build-Number",
        "audiences": [
          {
            "id": "37dbd115-9223-46c0-979f-07dc2c0b5690",
            "name": "Early-Access"
          }
        ],
        "value": "71",
        "create_time_sec": 1748860768,
        "update_time_sec": 1748861025,
        "flag_id": "c29ab570-b9ba-47f9-ba6a-1e48762f34fd"
      }
    ],
    "value": "70",
    "update_time_sec": 1748861087,
    "create_time_sec": 1748860768,
    "schema_id": "00000000-0000-0000-0000-000000000003"
  },
  "event": "FEATURE_FLAG_UPDATED"
}
```

The **data** object may include the following fields:
- **id:** The unique identifier of the feature flag.
- **name:** The name of the feature flag.
- **variants:** An array of variants, each containing variant ID, name, and value.
- **value:** The feature flag's value.
- **create_time_sec:** The creation timestamp as UNIX seconds.
- **update_time_sec:** The last update timestamp as UNIX seconds.
- **schema_id:** The ID of the schema for the flag's value fields (refer to "Taxonomy" > "Schema Validators").
- **description:** The feature flag's description.

The **FEATURE_FLAG_UPDATED** webhook is triggered for any change to the feature flag, including name, description, category, or value changes (excluding variant-specific changes). It provides the updated feature flag payload. To track changes, store previous webhook payloads and compare them with new ones.

### Feature flag variant webhooks
There are three webhook event types related to Feature Flag Variants:
- `FEATURE_FLAG_VARIANT_CREATED`
- `FEATURE_FLAG_VARIANT_UPDATED`
- `FEATURE_FLAG_VARIANT_DELETED`

All three events provide similar information in the webhook payload. Here's an example request body:

```json
{
  "id": "52a23ea2-aabe-411f-befa-3d864da7d478",
  "data": {
    "id": "4fa27911-d5dd-4117-9676-057690cdc9d8",
    "name": "Early-Access-Build-Number",
    "audiences": [
      {
        "id": "37dbd115-9223-46c0-979f-07dc2c0b5690",
        "name": "Early-Access"
      }
    ],
    "value": "71",
    "create_time_sec": 1748860768,
    "update_time_sec": 1748861025,
    "flag_id": "c29ab570-b9ba-47f9-ba6a-1e48762f34fd"
  },
  "event": "FEATURE_FLAG_VARIANT_UPDATED"
}
```

The **data** object may include:
- **id:** The unique identifier of the variant.
- **name:** The variant's name.
- **audiences:** An array of audiences targeted by this variant.
- **value:** The variant's value.
- **create_time_sec:** The creation timestamp as UNIX seconds.
- **update_time_sec:** The update timestamp as UNIX seconds.
- **flag_id:** The unique ID of the parent feature flag.

The **FEATURE_FLAG_VARIANT_UPDATED** webhook is triggered for any change to the variant, including updates to its audience list.
