> ## Documentation Index
> Fetch the complete documentation index at: https://docs.immutable.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Receive real-time notifications when players link or disconnect external accounts

## Overview

Audience webhooks notify your server when a player links or disconnects an external account (Steam, Discord, Epic Games, etc.) within a project you manage. Events are delivered as signed HTTP POST requests to an endpoint you configure in Hub.

## Configure in Hub

Go to [Hub](https://hub.immutable.com) and open **Settings** > **Webhooks** > **Add Webhook**.

Select one or both audience events:

| Event                       | Fired when                               |
| --------------------------- | ---------------------------------------- |
| `audience_account_linked`   | A player links an external account       |
| `audience_account_unlinked` | A player disconnects an external account |

## Payload

Both events use the same envelope shape. The `data` field contains the player and account details.

```json theme={null}
{
  "id": "evt_4cea103e-9148-476e-b634-5a31b4dc58de",
  "type": "audience_account_linked",
  "api_version": "2026-05-01",
  "created_at": "2026-05-01T10:30:00.000000000Z",
  "project_id": "353b1f6c-7dde-460c-9e5a-0711949e71f0",
  "data": {
    "player": {
      "id": "auth0|abc123"
    },
    "linked_account": {
      "provider": "steam",
      "provider_account_id": "76561198728595133",
      "username": "playername",
      "linked_at": "2026-05-01T10:29:58Z"
    }
  }
}
```

For unlink events, `type` is `audience_account_unlinked`. All other fields are identical.

**Supported `provider` values:** `steam`, `discord`, `epic_games`, `x`, `telegram`, `tiktok`, `twitch`

## Verify the Signature

Webhooks are signed by AWS SNS using RSA. Verify signatures using the [`sns-validator`](https://www.npmjs.com/package/sns-validator) npm package. There is no shared secret or custom header.

SNS delivers messages with `Content-Type: text/plain`. If you use Express, configure a raw text body parser for your webhook route:

```typescript theme={null}
app.use('/webhooks/immutable', express.text({ type: '*/*' }));
app.use('/webhooks/immutable', (req, res, next) => {
  if (typeof req.body === 'string') req.body = JSON.parse(req.body);
  next();
});
```

Then verify the signature and topic ARN:

```typescript theme={null}
import MessageValidator from 'sns-validator';

const validator = new MessageValidator();
const TOPIC_ARN = 'arn:aws:sns:us-east-2:362750628221:webhook-outbound-prod';

app.post('/webhooks/immutable', (req, res) => {
  res.status(200).send('OK');

  validator.validate(req.body, (err, message) => {
    if (err) {
      console.error('Invalid SNS signature', err);
      return;
    }

    if (message.TopicArn !== TOPIC_ARN) {
      console.error('Unexpected topic ARN', message.TopicArn);
      return;
    }

    if (message.Type === 'SubscriptionConfirmation') {
      fetch(message.SubscribeURL);
      return;
    }

    const event = JSON.parse(message.Message);
    handleEvent(event);
  });
});
```

Checking `TopicArn` confirms the event originated from Immutable's infrastructure and not a third-party SNS topic.

## Retry Policy

SNS retries failed deliveries automatically according to the [SNS delivery retry policy](https://docs.aws.amazon.com/sns/latest/dg/sns-message-delivery-retries.html). There is no manual retry in Hub. If your endpoint is unavailable for an extended period, events may be permanently dropped.

## Best Practices

* **Respond quickly.** Return `200` to SNS immediately and process the event asynchronously to avoid delivery timeouts.
* **Deduplicate.** Use the `id` field to detect duplicate deliveries. The same event may arrive more than once.
* **Scope by project.** The `project_id` field identifies which of your projects the player belongs to. A player can appear in multiple projects and trigger events for each.
