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 Webbook
Create Webhook

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).
  • 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
Webhook EventDescription
LIVE_EVENT_STARTEDTriggered when a live event run begins.
LIVE_EVENT_ENDEDTriggered when a live event run ends.
SCHEDULED_MESSAGE_SENTTriggered when a scheduled message is sent.
FEATURE_FLAG_CREATEDTriggered when a new feature flag is created.
FEATURE_FLAG_UPDATEDTriggered when a feature flag is updated.
FEATURE_FLAG_DELETEDTriggered when a feature flag is deleted.
FEATURE_FLAG_VARIANT_CREATEDTriggered when a new feature flag variant is created.
FEATURE_FLAG_VARIANT_UPDATEDTriggered when a feature flag variant is updated, including changes to its audience definitions.
FEATURE_FLAG_VARIANT_DELETEDTriggered when a feature flag variant is deleted.
  1. 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 will be taken to its details page.

Webhook Details
Webhook Details

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
Webhook Attempts

Using the Signing Secret #

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

Below is an example JavaScript snippet to verify the signature:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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.

Send Test Event
Send Test Event

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
{
  "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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
{
  "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 will receive similar information in your webhook. You can find an example of the request body below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
  "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 is an example request body:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
{
  "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.