> ## Knowledge Base Index
> Fetch the complete knowledge base index at: https://docs.bloy.io/sitemap.xml
> Use this file to discover available pages before exploring further.
> Pure-Markdown content can be obtained by appending a '.md' suffix to the content URLs listed in the sitemap (without the trailing slash).

# API Reference

# API reference
Base URL: https://api.bloy.io/rest-api/v1/webhooks/subscription
---
## Create Subscription

`POST` https://api.bloy.io/rest-api/v1/webhooks/subscription

Register an endpoint for a topic. Returns the signing secret — the only time it is ever exposed.

**Headers**
| Name | Type | Required | Example |
| ---- |
| `Authorization` | string | Required | `Bearer YOUR_PUBLIC_API_KEY` |
| `Content-Type` | string | Required | `application/json` |

**Body**
| Name | Type | Required | Description |
| ---- |
| `topic` | string | Required | A topic from [Webhook Events](https://docs.bloy.io/en/article/webhook-events-105rxx8/) |
| `callbackUrl` | string | Required | HTTPS URL |

**Request**
```
curl -X POST https://api.bloy.io/rest-api/v1/webhooks/subscriptions \
  -H "Authorization: Bearer YOUR_PUBLIC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "topic": "points/earned",
    "callbackUrl": "https://example.com/webhooks/bloy"
  }'
```

**Response** `201 Created`
```
{
  "success": true,
  "message": "OK",
  "subscription": {
    "id": "66f3d8b1c7a4e90218ba5c33",
    "topic": "points/earned",
    "callbackUrl": "https://example.com/hooks/bloy",
    "previousSecretValidUntil": null,
    "createdAt": "2026-08-25T08:02:11.004Z",
    "updatedAt": "2026-08-25T08:02:11.004Z",
    "signingSecret": "3f9a1c8e...64_hex_characters...b02d7e14"
  }
}
```
| Field | Type | Description |
| ---- |
| `id` | string | Subscription id |
| `topic` | string | The subscribed topic or wildcard |
| `callbackUrl` | string | Where deliveries are sent |
| `previousSecretValidUntil` | string | End of the rotation grace period, `null` if never rotated |
| `signingSecret` | string | 64 hex characters. **Returned once — store it now** |

**Errors**
| Status | Cause |
| ---- |
| `400 Bad Request` | Unknown topic, non-HTTPS URL, or a URL pointing at an internal host |
| `409 Conflict` | This shop already has a subscription with the same `topic` and `callbackUrl` |
---
## List Subscriptions

`GET` https://api.bloy.io/rest-api/v1/webhooks/subscriptions

Get every subscription registered for your shop, newest first. Signing secrets are never included.

**Request**
```
curl https://<your-bloy-api-host>/rest-api/v1/webhooks/subscriptions \
  -H "Authorization: Bearer YOUR_PUBLIC_API_KEY"
```

**Response** `200 OK`
```
{
  "success": true,
  "message": "OK",
  "subscriptions": [
    {
      "id": "66f3d8b1c7a4e90218ba5c33",
      "topic": "points/earned",
      "callbackUrl": "https://example.com/hooks/bloy",
      "previousSecretValidUntil": null,
      "createdAt": "2026-08-25T08:02:11.004Z",
      "updatedAt": "2026-08-25T08:02:11.004Z"
    },
    {
      "id": "66f3d7a0c7a4e90218ba5c2f",
      "topic": "tier/*",
      "callbackUrl": "https://example.com/hooks/bloy-vip",
      "previousSecretValidUntil": "2026-08-26T04:30:00.000Z",
      "createdAt": "2026-08-24T11:20:45.881Z",
      "updatedAt": "2026-08-25T04:30:00.000Z"
    }
  ]
}
```
---
## Update Subscription

`PUT` https://api.bloy.io/rest-api/v1/webhooks/subscriptions/{id}

Change where a subscription delivers. The topic and the signing secret are unchanged — to change the topic, delete the subscription and create a new one.

**Path parameters**
| Name | Type | Required | Description |
| ---- |
| `id` | string | Required | Subscription id |

**Body**
| Name | Type | Required | Description |
| ---- |
| `callbackUrl` | string | Required | New HTTPS URL |

**Request**
```
curl -X PUT https://<your-bloy-api-host>/rest-api/v1/webhooks/subscriptions/66f3d8b1c7a4e90218ba5c33 \
  -H "Authorization: Bearer YOUR_PUBLIC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "callbackUrl": "https://example.com/hooks/bloy-v2" }'
```

**Response** `200 OK`
```
{
  "success": true,
  "message": "OK",
  "subscription": {
    "id": "66f3d8b1c7a4e90218ba5c33",
    "topic": "points/earned",
    "callbackUrl": "https://example.com/hooks/bloy-v2",
    "previousSecretValidUntil": null,
    "createdAt": "2026-08-25T08:02:11.004Z",
    "updatedAt": "2026-08-25T09:15:38.442Z"
  }
}
```

**Errors**
| Status | Cause |
| ---- |
| `400 Bad Request` | Malformed id, or a `callbackUrl` that fails validation |
| `404 Not Found` | No such subscription on your shop |
---
## Rotate Signing Secret

`POST` https://api.bloy.io/rest-api/v1/webhooks/subscriptions/{id}/rotate-secret

Issue a new signing secret while keeping the previous one valid for 24 hours.

**Path parameters**
| Name | Type | Required | Description |
| ---- |
| `id` | string | Required | Subscription id |

**Body**
| Name | Type | Required | Description |
| ---- |
| `graceHours` | integer | Optional | How long the previous secret stays valid, `0`–`24`. Defaults to `24`. Send `0` to revoke it immediately |

**Request**
```
curl -X POST \
  https://<your-bloy-api-host>/rest-api/v1/webhooks/subscriptions/66f3d8b1c7a4e90218ba5c33/rotate-secret \
  -H "Authorization: Bearer YOUR_PUBLIC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "graceHours": 2 }'
```

**Response** `201 Created`
```
{
  "success": true,
  "message": "OK",
  "subscription": {
    "id": "66f3d8b1c7a4e90218ba5c33",
    "topic": "points/earned",
    "callbackUrl": "https://example.com/hooks/bloy",
    "previousSecretValidUntil": "2026-08-26T09:41:02.771Z",
    "createdAt": "2026-08-25T08:02:11.004Z",
    "updatedAt": "2026-08-25T09:41:02.771Z",
    "signingSecret": "c71b4e02...64_hex_characters...9ad35f68"
  }
}
```
| Field | Type | Description |
| ---- |
| `signingSecret` | string | The new secret. **Returned once — store it now** |
| `previousSecretValidUntil` | string | When the old secret stops being accepted, 24 hours out |

**Errors**
| Status | Cause |
| ---- |
| `400 Bad Request` | Malformed id |
| `404 Not Found` | No such subscription on your shop |
---
## Delete Subscription

`DELETE` https://api.bloy.io/rest-api/v1/webhooks/subscriptions/{id}

Stop delivering to an endpoint. Existing delivery records are kept and remain queryable.

**Request**
```
curl -X DELETE \
  https://<your-bloy-api-host>/rest-api/v1/webhooks/subscriptions/66f3d8b1c7a4e90218ba5c33 \
  -H "Authorization: Bearer YOUR_PUBLIC_API_KEY"
```

**Response** `200 OK`
```
{
  "success": true,
  "message": "OK"
}
```

**Errors**
| Status | Cause |
| ---- |
| `400 Bad Request` | Malformed id |
| `404 Not Found` | No such subscription on your shop |
---
## Rate limits
Limits are per shop, measured over a rolling 60-second window.
| Endpoint | Limit |
| ---- |
| `GET`/`POST`/`PUT`/`DELETE` on `/webhooks/subscriptions` | 30 requests per minute |
| `POST /webhooks/subscriptions/{id}/rotate-secret` | 5 requests per minute |
| `GET /webhooks/deliveries` | 60 requests per minute |
| `POST /webhooks/deliveries/{id}/replay` | 10 requests per minute |

Exceeding a limit returns `429 Too Many Requests`. Back off and retry.