# user.authorized
URL: https://musicapi.com/docs/reference/events/user.authorized.md

A webhook event: we POST it to your endpoint. Product: MusicAPI Managed Transfers.

Sent with the headers `X-MusicAPI-Event: user.authorized` and `X-MusicAPI-Signature: t=<seconds>,v1=<hex HMAC-SHA256 of "t.body">`.

## Headers

| Header | Value |
| --- | --- |
| `Content-Type` | `application/json` |
| `X-MusicAPI-Event` | The event name, for example `transfer.completed`. |
| `X-MusicAPI-Signature` | `t=<seconds>,v1=<hex>`. The hex is the HMAC-SHA256 of `<t>.<raw body>` with your endpoint secret. |

## Payload

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | no | The id of this delivery, `evt_<uuid>`. A retry sends the same id, so dedupe by it. Two endpoints get two different ids for the same event. Deliveries queued before 2026-10-16 have no `id`. |
| `event` | string | yes | One of: user.authorized. |
| `createdAt` | date-time | yes |  |
| `data` | object | yes |  |
| `data.sessionUuid` | uuid | yes |  |
| `data.role` | string | yes | One of: source, target. |
| `data.integrationType` | string \| null | yes |  |
| `data.integrationUserUuid` | uuid | yes |  |
| `data.preauthorized` | boolean | yes |  |
| `data.externalUserId` | string | no | Only for a pre-authorized session. |

## Example payload

```json
{
  "id": "evt_3d1f6c0e-8a52-4c1b-9a0e-2f4b7c9d1e33",
  "event": "user.authorized",
  "createdAt": "2026-10-16T09:41:07.000Z",
  "data": {
    "sessionUuid": "5b0c6f0e-2c8a-4d0e-9a51-7e3f1c2d9b44",
    "role": "source",
    "integrationType": "string",
    "integrationUserUuid": "5b0c6f0e-2c8a-4d0e-9a51-7e3f1c2d9b44",
    "preauthorized": true,
    "externalUserId": "string"
  }
}
```

## Delivery and retries

Answer with a 2xx status. We try other answers again, up to 5 attempts in total, with waits of 60 s, 120 s, 240 s, 480 s.

## Verify the signature

Compute the HMAC-SHA256 of `<t>.<raw body>` with your endpoint secret, compare it with `v1` in constant time, and reject a `t` older than 5 minutes.

Node:

```js
import crypto from "node:crypto";

// header: "t=<sec>,v1=<hex>"
export function verify(rawBody, header, secret) {
  const { t, v1 } = Object.fromEntries(
    header.split(",").map((p) => p.split("="))
  );
  // Reject an old timestamp, for example after 5 minutes.
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${t}.${rawBody}`)
    .digest("hex");
  if (expected.length !== String(v1).length) return false;
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}
```

Python:

```python
import hashlib
import hmac
import time

# header: "t=<sec>,v1=<hex>"
def verify(raw_body: bytes, header: str, secret: str) -> bool:
    parts = dict(item.split("=", 1) for item in header.split(","))
    t, v1 = parts.get("t", ""), parts.get("v1", "")
    # Reject an old timestamp, for example after 5 minutes.
    if not t.isdigit() or abs(time.time() - int(t)) > 300:
        return False
    expected = hmac.new(
        secret.encode(), t.encode() + b"." + raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, v1)
```

Go:

```go
import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"math"
	"strconv"
	"strings"
	"time"
)

// header: "t=<sec>,v1=<hex>"
func Verify(rawBody []byte, header, secret string) bool {
	var t, v1 string
	for _, part := range strings.Split(header, ",") {
		key, value, _ := strings.Cut(part, "=")
		if key == "t" {
			t = value
		} else if key == "v1" {
			v1 = value
		}
	}
	sec, err := strconv.ParseInt(t, 10, 64)
	// Reject an old timestamp, for example after 5 minutes.
	if err != nil || math.Abs(float64(time.Now().Unix()-sec)) > 300 {
		return false
	}
	mac := hmac.New(sha256.New, []byte(secret))
	mac.Write([]byte(t + "."))
	mac.Write(rawBody)
	expected := hex.EncodeToString(mac.Sum(nil))
	return hmac.Equal([]byte(expected), []byte(v1))
}
```
