# Authentication and Signatures

Vask uses Pusher-compatible signing for channel authorization, user authentication, webhooks, and HTTP API requests.

Use your Vask `app_key` anywhere a Pusher SDK asks for an app key. If a server SDK also asks for `app_id`, use the same `app_key`.

## Channel authorization

Private, presence, private cache, presence cache, encrypted, and encrypted cache channels require authorization.

The client SDK sends your application server:

```text
socket_id=1234.5678
channel_name=private-dashboard.42
```

For private channels, sign this string with HMAC-SHA256 using the app secret:

```text
1234.5678:private-dashboard.42
```

Return:

```json
{
    "auth": "app_key:hmac_signature"
}
```

## Presence authorization

Presence channels add `channel_data`. The exact JSON string you return must be the exact JSON string you sign.

```json
{
    "user_id": "user-123",
    "user_info": {
        "name": "Ada"
    }
}
```

Sign:

```text
1234.5678:presence-room.42:{"user_id":"user-123","user_info":{"name":"Ada"}}
```

Return:

```json
{
    "auth": "app_key:hmac_signature",
    "channel_data": "{\"user_id\":\"user-123\",\"user_info\":{\"name\":\"Ada\"}}"
}
```

## User authentication

User authentication identifies the connection itself. It powers server-to-user events, watchlist events, and user connection termination.

The auth endpoint signs:

```text
1234.5678::user::{"id":"user-123","name":"Ada"}
```

Return:

```json
{
    "auth": "app_key:hmac_signature",
    "user_data": "{\"id\":\"user-123\",\"name\":\"Ada\"}"
}
```

The client then sends `pusher:signin` over the WebSocket connection.

## HTTP API authentication

Every request to `https://api.vask.dev/apps/{app_key}/...` must be signed.

Required query parameters:

| Parameter        | Description                                                   |
| ---------------- | ------------------------------------------------------------- |
| `auth_key`       | Your Vask `app_key`.                                          |
| `auth_timestamp` | Unix timestamp in seconds.                                    |
| `auth_version`   | Use `1.0`.                                                    |
| `body_md5`       | MD5 hex digest of the request body for non-empty POST bodies. |
| `auth_signature` | HMAC-SHA256 hex digest of the string to sign.                 |

String to sign:

```text
METHOD
/apps/{app_key}/events
auth_key=app_key&auth_timestamp=1715520000&auth_version=1.0&body_md5=...
```

Node example:

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

function signRequest({ method, path, params, secret }) {
    const query = Object.keys(params)
        .sort()
        .map((key) => `${key}=${params[key]}`)
        .join('&');

    return crypto
        .createHmac('sha256', secret)
        .update(`${method.toUpperCase()}\n${path}\n${query}`)
        .digest('hex');
}
```

## Webhook signatures

Webhook deliveries use the app secret to sign the raw request body.

```text
X-Pusher-Key: app_key
X-Pusher-Signature: hmac_sha256(raw_body, app_secret)
```

Do not parse and re-encode the body before verification.

## Common failures

| Symptom                             | Check                                                                                        |
| ----------------------------------- | -------------------------------------------------------------------------------------------- |
| `401` from HTTP API                 | Wrong secret, stale timestamp, missing `body_md5`, or signed path differs from request path. |
| `pusher:error` on private subscribe | Auth endpoint signed the wrong `socket_id` or `channel_name`.                                |
| Presence user missing               | `channel_data` was not included, not valid JSON, or did not include `user_id`.               |
| User termination does nothing       | The target connection has not completed `pusher:signin`.                                     |

## Related docs

- [User authentication](/docs/user-authentication)
- [HTTP API reference](/docs/http-api)
- [Webhooks](/docs/webhooks)
