# Signing Keys | AudioDN Docs

> Sign delivery URLs on your own server with an org-scoped HMAC secret, bypassing play sessions for public tracks.

Source: https://audiodeliverynetwork.com/docs/api/signing-keys/

---

# Signing Keys

A Signing Key is a per-organization HMAC secret that lets you sign delivery URLs directly on your own server — no play session, no Authorization header, no round trip to the AudioDN API. Requests go straight to your delivery domain (`{organization_id}.audiodelivery.net`), where AudioDN verifies the signature at the edge and serves your audio. This is ideal for publicly available tracks where you control who receives a link.

#### Public access

Anyone with a validly signed URL can stream the file until the signature expires. Keep signing on your server, choose a sensible signature lifetime, and delete keys you no longer use. Every request counts toward play usage and billing.

## Creating a Signing Key

In the [AudioDN dashboard](https://account.audiodeliverynetwork.com), go to **Settings → API Keys** and create a **URL Signing** key. You will be shown the secret **once** — store it securely on your server. You can optionally set a signature lifetime and limit the key to specific variants.

#### Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `title` | string | Required | A name to identify the key |
| `ttl` | number | Optional | Signature lifetime in seconds (default 10800 = 3 hours). Must match the value used when signing. |
| `variants` | string\[\] | Optional | Restrict the key to specific variant indexes (e.g. \["lq","hq"\]). A key with no variants can sign any file on your domain. |

## How It Works

-   When you create a key, AudioDN registers your signing secret at the edge, scoped to your delivery domain (`{org}.audiodelivery.net`). The plaintext secret is shown to you once and is never stored where it can be read back.
-   On your server, you compute an HMAC-SHA256 token over the request path (and query string) plus the current timestamp, then append it as a `verify` query parameter.
-   When the request hits your delivery domain, AudioDN recomputes the HMAC from your secret and confirms it matches _and_ that the timestamp is still within the signature lifetime. Valid, unexpired requests are served immediately.
-   Invalid or expired signatures fall through to the standard protection (the normal play-session flow) and are rejected.

Verification uses the standard timed-HMAC scheme (`is_timed_hmac_valid_v0`), so any HMAC-SHA256 implementation that produces the URL format below will validate.

## Your Delivery Domain

Every organization gets a dedicated delivery subdomain in the form `{organization_id}.audiodelivery.net`, where AudioDN serves your organization's audio. This is the host you sign against and the host your users fetch audio from. You can find and copy your exact domain in the [AudioDN dashboard](https://account.audiodeliverynetwork.com) under **Settings → Organization → Delivery Domain**.

A file's full URL is your delivery domain plus the file's path, for example:

```
https://1bb2f0c4-....audiodelivery.net/7e4386f0-..../20260709145408659.uSYrzmRWTDvEyjFz_lq.aac
```

## Signed URL Format

```
https://{org_domain}/{file_path}?verify={issued}-{base64url_mac}
```

`issued` is the Unix timestamp (seconds) when you signed. `{base64url_mac}` is the URL-safe Base64 (no padding) HMAC. The `verify` parameter must always be last.

## Signing on Your Server

The message you sign is the URL path plus any existing query string (excluding `verify`), followed by the timestamp. This reference implementation uses the Web Crypto API and works in Node 18+, Deno, Bun, Cloudflare Workers, and the browser:

```
function base64url(bytes) {
  let binary = '';
  const b = new Uint8Array(bytes);
  for (let i = 0; i < b.byteLength; i++) binary += String.fromCharCode(b[i]);
  return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}

async function hmacSha256(key, data) {
  const cryptoKey = await crypto.subtle.importKey(
    'raw', new TextEncoder().encode(key),
    { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']
  );
  return new Uint8Array(await crypto.subtle.sign('HMAC', cryptoKey, new TextEncoder().encode(data)));
}

// secret  = the signing key secret from the dashboard
// domain  = your org delivery host, e.g. "1bb2....audiodelivery.net"
// path    = the file path on your delivery domain, e.g. "folder-id/track-index_lq.mp3"
async function signUrl(secret, domain, path) {
  const u = new URL('https://' + domain + '/' + path);
  u.searchParams.delete('verify');

  const message = u.pathname + (u.search || '');
  const issued = Math.floor(Date.now() / 1000).toString();

  const mac = await hmacSha256(secret, message + issued);
  u.searchParams.append('verify', issued + '-' + base64url(mac)); // verify MUST be last

  return u.toString();
}
```

The signature lifetime is enforced at the edge (the `ttl` you set when creating the key), so you do not embed an expiry — only the issued timestamp.

## Variant Scoping

When you limit a key to specific variants, the edge only accepts signatures for files whose path contains an allowed variant suffix. Files are named `{track_index}_{variant_index}.{ext}` and stored at `{folder_id}/{file_name}`, so a request for the `lq` variant always contains `_lq.` in its path. Requests signed for any other variant fall through to the standard protection and are rejected.

## Plays & Reporting

Signed URLs deliver audio directly from your delivery domain and **bypass the play session** — there is no call to the AudioDN API to mint a session token, and no per-play authorization round trip. This is what makes them fast and simple for public tracks.

Even though the play session is bypassed, your **play statistics remain accurate**. Plays in your Reporting dashboard are measured from actual audio delivery on your domain, aggregated hourly. Whether a file is fetched through a play session or a server-signed HMAC URL, the underlying delivery is the same, so both are counted.

#### Usage & billing still apply

Because every signed request is a real delivery, it counts toward your play usage and billing just like a play-session stream. Choose a sensible signature lifetime and delete keys you no longer use to keep control over access.
