# Player link notices

When a player unlinks an account in PlayerPal, PlayerPal can tell the affected operator or game provider straight away, so your platform stops showing the player as connected. The notice is a convenience, not a guarantee: your integration must also reconcile alias status on its own, because a notice can be missed.

:::info[In development: delivery is limited]
Today notices are delivered only to a demonstration operator integration. Your platform will not receive them yet. Per-partner notice URLs and per-partner notice credentials are planned. Until then, reconcile alias status (step 4 below), which is available to every partner.
:::

## Events

| Event | Sent when | Request | Status |
|---|---|---|---|
| Player link notice (`status: revoked`) | A player unlinks your operator or game provider integration in PlayerPal | `POST /v1/player-link-notices` on your endpoint | [In development] |

There are no other events today. Activity, facts and Points have no callbacks: check a batch with its status route instead.

## How it works

```mermaid
sequenceDiagram
    participant P as Player
    participant PP as PlayerPal
    participant O as Your platform

    P->>PP: Unlink account
    PP->>PP: Revoke link and aliases
    PP-->>P: Unlink confirmed (does not wait for you)
    PP-)O: POST player link notice (one attempt)
    O-->>PP: 2xx
    Note over O: Later, on next status refresh
    O->>PP: GET /v1/aliases/{playerAlias}
    PP-->>O: status active or revoked, or 404
```

- The Unlink completes in PlayerPal first. The notice is sent afterwards, and the player's Unlink never waits for, or fails because of, your endpoint.
- Unlinking an operator also revokes the game provider aliases issued through that operator's integrations. Each revoked alias produces its own notice.
- A repeated Unlink request does not send the notice again.

## Receive the notice

PlayerPal sends `POST /v1/player-link-notices` to your endpoint with a JSON body and `content-type: application/json`.

| Field | Type | Meaning |
|---|---|---|
| `audience_id` | string, required | The public ID of the audience the alias was issued to: your operator ID, or the game provider integration's ID |
| `player_alias` | string, required | The alias you hold for this player: `pp_plr_opr_…` for operators, `pp_plr_prv_…` for game providers |
| `alias_type` | string, required | `operator` or `provider_operator` |
| `status` | string, required | Always `revoked` |

The notice never contains PlayerPal's internal player ID or any personal data. Look the player up by `player_alias`.

POST /v1/player-link-notices:

```
{
  "audience_id": "pp_opr_XXXX",
  "player_alias": "pp_plr_opr_XXXX",
  "alias_type": "operator",
  "status": "revoked"
}
```

## Authenticate it

Today a notice carries one credential: an `x-api-key` header with the integration key. Notices are not signed.

1. Compare `x-api-key` with your integration key in constant time.
2. Reject the request with `401` if it does not match.
3. Accept notices only over TLS, and keep the integration key server-side.

:::warning[Planned]
Signed notices with per-partner secrets are planned. Until then, the integration key is the only check.
:::

Notice headers:

```
POST /v1/player-link-notices
content-type: application/json
x-api-key: <your-integration-key>
```

## Respond quickly

Your handler should:

1. Find the alias by `player_alias` and `audience_id`, and mark it unlinked. Treat an unknown alias as success.
2. Return `204` (or any `2xx`) within 2 seconds. Do any slower work afterwards.
3. Be idempotent: receiving the same notice twice must leave the same result.

| Aspect | Current behaviour |
|---|---|
| Timeout | PlayerPal waits 2 seconds for your response |
| Success | Any `2xx` status |
| Retries | None. A failed or timed-out notice is logged and not resent |
| Ordering | Not guaranteed across aliases |
| Delivery | Demonstration integration only (see above) |

Your response:

```
HTTP/1.1 204 No Content
```

## Reconcile alias status

[Available]

`getAliasStatus` (see the API reference)

Because notices are limited and not retried, reconcile when you next display the player's link state, for example when they open their account page or launch a game from your lobby. Never call this per wager.

Send the integration key with `x-operator-id` (operators) or `x-integration-id` (game providers). See [Authentication](./authentication.mdx).

| Response | What to do |
|---|---|
| `200` `{ "status": "active" }` | Keep the link |
| `200` `{ "status": "revoked" }` | Mark the link as unlinked, as you would for a notice |
| `404 alias_not_found` | Treat as unlinked |
| Network error or `5xx` | Keep your last known state and try again later |

PlayerPal being unreachable must never block play: show the last known state and move on.

For an alias you hold, you get `200` with `status` `active`. After the player unlinks, the same call returns `revoked`.

(Code sample: alias-status. See the page for curl, Node.js, Python, PHP, Java and C#.)

Response:

```
{ "status": "revoked" }
```

Alias not issued to you:

```
{
  "statusCode": 404,
  "message": "alias_not_found",
  "error": "Not Found"
}
```

## Related

  - [Link players](./linking-players.mdx): 
    How players connect accounts and how aliases are issued.
  
  - [Idempotency and errors](./idempotency-and-errors.mdx): 
    Every error code, including the alias status errors.
  
  - [Design for availability](./availability.mdx): 
    Why link state never sits in the play path.
