# Webhooks

Signed delivery of catalog changes to your own endpoint. An endpoint subscribes to events (`firearm.updated`, `catalog.resynced` and the rest) and receives the same payload `GET /v1/firearms/{id}` returns, so a mirror can apply a delivery without re-fetching. Every request carries a signature made with the secret `create()` returns once. Failed deliveries are retried on a schedule, so a brief outage at your end costs nothing.

Source: https://docs.gunspec.io/en/sdk/webhooks

## client.webhooks.list()

`GET /v1/me/webhooks`

Auth: API key required  
Tier: Explorer+

The account's endpoints with their subscribed events and delivery health.

### Query parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `page` | integer | no | Page number, from 1 to 10,000 |
| `per_page` | integer | no | Items per page (max 100) |

### Response 200 (application/json)

| Field | Type | Description |
| --- | --- | --- |
| `data` | WebhookEndpoint[] | One page of results |
| `data[].id` | string | Opaque endpoint id. Address it at `/v1/me/webhooks/{endpointId}`. |
| `data[].url` | string | Absolute, directly fetchable URL. |
| `data[].events` | string[] | Event types this endpoint is subscribed to. The full list is on the `POST` operation. |
| `data[].description` | string \| null | Prose summary of the record, where one has been written. |
| `data[].active` | boolean | False while the endpoint is paused. A paused endpoint receives nothing and is not retried. |
| `data[].createdAt` | string | When the record was first added, `YYYY-MM-DD HH:MM:SS` in UTC. |
| `data[].updatedAt` | string | When the endpoint was last changed, ISO 8601. |
| `pagination.page` | number | Current page number |
| `pagination.per_page` | number | Items per page |
| `pagination.total` | number | Total matching records (Builder and above) |

```json
{
  "success": true,
  "data": [
    {
      "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "url": "https://example.com/hooks/gunspec",
      "events": [
        "firearm.created"
      ],
      "description": null,
      "active": true,
      "createdAt": "2026-08-23T02:00:00.000Z",
      "updatedAt": "2026-08-23T02:00:00.000Z"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "per_page": 20,
    "total": 42,
    "totalPages": 3
  }
}
```

### Errors

| Status | Code | Message |
| --- | --- | --- |
| 400 | `VALIDATION_ERROR` | Validation error |
| 401 | `UNAUTHORIZED` | API key required |
| 403 | `FORBIDDEN` | Valid key, not permitted |
| 429 | `RATE_LIMITED` | Rate limit exceeded |
| 500 | `INTERNAL_ERROR` | Unexpected server error |

### Example

```bash
curl --request GET \
  --url 'https://api.gunspec.io/v1/me/webhooks' \
  --header 'X-API-Key: your_key'
```

## client.webhooks.create()

`POST /v1/me/webhooks`

Auth: API key required  
Tier: Explorer+

The response is the only one that carries the signing `secret`; store it.

### Request body (application/json)

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `url` | string | yes | Where deliveries are POSTed. A public https hostname on port 443: no IP address, no credentials in the URL, and not a gunspec.io or workers.dev host. Redirects are not followed, so register the final URL. At most 2,048 characters. |
| `events` | string[] | yes | Event types this endpoint is subscribed to. The full list is on the `POST` operation. |
| `description` | string | no | Prose summary of the record, where one has been written. |

```json
{
  "url": "https://example.com/hooks/gunspec",
  "events": [
    "firearm.created"
  ],
  "description": "Production ingest"
}
```

### Response 201 (application/json)

| Field | Type | Description |
| --- | --- | --- |
| `data` | WebhookEndpointCreated | The response payload |
| `data.id` | string | Opaque endpoint id. Address it at `/v1/me/webhooks/{endpointId}`. |
| `data.url` | string | Absolute, directly fetchable URL. |
| `data.events` | string[] | Event types this endpoint is subscribed to. The full list is on the `POST` operation. |
| `data.description` | string \| null | Prose summary of the record, where one has been written. |
| `data.active` | boolean | False while the endpoint is paused. A paused endpoint receives nothing and is not retried. |
| `data.createdAt` | string | When the record was first added, `YYYY-MM-DD HH:MM:SS` in UTC. |
| `data.updatedAt` | string | When the endpoint was last changed, ISO 8601. |
| `data.secret` | string | HMAC signing secret. Returned only here, on creation. No other endpoint will show it again. |

```json
{
  "success": true,
  "data": {
    "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "url": "https://example.com/hooks/gunspec",
    "events": [
      "firearm.created"
    ],
    "description": null,
    "active": true,
    "createdAt": "2026-08-23T02:00:00.000Z",
    "updatedAt": "2026-08-23T02:00:00.000Z",
    "secret": "whsec_..."
  }
}
```

### Errors

| Status | Code | Message |
| --- | --- | --- |
| 400 | `VALIDATION_ERROR` | Validation error |
| 401 | `UNAUTHORIZED` | API key required |
| 403 | `FORBIDDEN` | Valid key, not permitted |
| 413 | `PAYLOAD_TOO_LARGE` | Request body too large |
| 429 | `RATE_LIMITED` | Rate limit exceeded |
| 500 | `INTERNAL_ERROR` | Unexpected server error |

### Example

```bash
curl --request POST \
  --url 'https://api.gunspec.io/v1/me/webhooks' \
  --header 'X-API-Key: your_key' \
  --header 'Content-Type: application/json' \
  --data '
{
  "url": "https://example.com/hooks/gunspec",
  "events": [
    "firearm.created"
  ],
  "description": "Production ingest"
}
'
```

## client.webhooks.get()

`GET /v1/me/webhooks/{endpointId}`

Auth: API key required  
Tier: Explorer+

One endpoint, with its recent delivery state.

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `endpointId` | string | yes | Webhook endpoint id |

### Response 200 (application/json)

| Field | Type | Description |
| --- | --- | --- |
| `data` | WebhookEndpoint | The response payload |
| `data.id` | string | Opaque endpoint id. Address it at `/v1/me/webhooks/{endpointId}`. |
| `data.url` | string | Absolute, directly fetchable URL. |
| `data.events` | string[] | Event types this endpoint is subscribed to. The full list is on the `POST` operation. |
| `data.description` | string \| null | Prose summary of the record, where one has been written. |
| `data.active` | boolean | False while the endpoint is paused. A paused endpoint receives nothing and is not retried. |
| `data.createdAt` | string | When the record was first added, `YYYY-MM-DD HH:MM:SS` in UTC. |
| `data.updatedAt` | string | When the endpoint was last changed, ISO 8601. |

```json
{
  "success": true,
  "data": {
    "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "url": "https://example.com/hooks/gunspec",
    "events": [
      "firearm.created"
    ],
    "description": null,
    "active": true,
    "createdAt": "2026-08-23T02:00:00.000Z",
    "updatedAt": "2026-08-23T02:00:00.000Z"
  }
}
```

### Errors

| Status | Code | Message |
| --- | --- | --- |
| 400 | `VALIDATION_ERROR` | Validation error |
| 401 | `UNAUTHORIZED` | API key required |
| 403 | `FORBIDDEN` | Valid key, not permitted |
| 404 | `NOT_FOUND` | Resource not found |
| 429 | `RATE_LIMITED` | Rate limit exceeded |
| 500 | `INTERNAL_ERROR` | Unexpected server error |

### Example

```bash
curl --request GET \
  --url 'https://api.gunspec.io/v1/me/webhooks/7c9e6679-7425-40de-944b-e07fc1f90ae7' \
  --header 'X-API-Key: your_key'
```

## client.webhooks.update()

`PUT /v1/me/webhooks/{endpointId}`

Auth: API key required  
Tier: Explorer+

Change an endpoint's URL, event list or enabled flag.

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `endpointId` | string | yes | Webhook endpoint id |

### Request body (application/json)

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `url` | string | no | Where deliveries are POSTed. A public https hostname on port 443: no IP address, no credentials in the URL, and not a gunspec.io or workers.dev host. Redirects are not followed, so register the final URL. At most 2,048 characters. |
| `events` | string[] | no | Event types this endpoint is subscribed to. The full list is on the `POST` operation. |
| `description` | string | no | Prose summary of the record, where one has been written. |
| `active` | boolean | no | False while the endpoint is paused. A paused endpoint receives nothing and is not retried. |

```json
{
  "url": "https://example.com/hooks/gunspec",
  "events": [
    "firearm.created"
  ],
  "description": "Production ingest",
  "active": true
}
```

### Response 200 (application/json)

| Field | Type | Description |
| --- | --- | --- |
| `data` | WebhookEndpoint | The response payload |
| `data.id` | string | Opaque endpoint id. Address it at `/v1/me/webhooks/{endpointId}`. |
| `data.url` | string | Absolute, directly fetchable URL. |
| `data.events` | string[] | Event types this endpoint is subscribed to. The full list is on the `POST` operation. |
| `data.description` | string \| null | Prose summary of the record, where one has been written. |
| `data.active` | boolean | False while the endpoint is paused. A paused endpoint receives nothing and is not retried. |
| `data.createdAt` | string | When the record was first added, `YYYY-MM-DD HH:MM:SS` in UTC. |
| `data.updatedAt` | string | When the endpoint was last changed, ISO 8601. |

```json
{
  "success": true,
  "data": {
    "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "url": "https://example.com/hooks/gunspec",
    "events": [
      "firearm.created"
    ],
    "description": null,
    "active": true,
    "createdAt": "2026-08-23T02:00:00.000Z",
    "updatedAt": "2026-08-23T02:00:00.000Z"
  }
}
```

### Errors

| Status | Code | Message |
| --- | --- | --- |
| 400 | `VALIDATION_ERROR` | Validation error |
| 401 | `UNAUTHORIZED` | API key required |
| 403 | `FORBIDDEN` | Valid key, not permitted |
| 404 | `NOT_FOUND` | Resource not found |
| 413 | `PAYLOAD_TOO_LARGE` | Request body too large |
| 429 | `RATE_LIMITED` | Rate limit exceeded |
| 500 | `INTERNAL_ERROR` | Unexpected server error |

### Example

```bash
curl --request PUT \
  --url 'https://api.gunspec.io/v1/me/webhooks/7c9e6679-7425-40de-944b-e07fc1f90ae7' \
  --header 'X-API-Key: your_key' \
  --header 'Content-Type: application/json' \
  --data '
{
  "url": "https://example.com/hooks/gunspec",
  "events": [
    "firearm.created"
  ],
  "description": "Production ingest",
  "active": true
}
'
```

## client.webhooks.delete()

`DELETE /v1/me/webhooks/{endpointId}`

Auth: API key required  
Tier: Explorer+

Delete an endpoint. Undelivered retries for it are dropped with it.

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `endpointId` | string | yes | Webhook endpoint id |

### Response 200 (application/json)

| Field | Type | Description |
| --- | --- | --- |
| `data` | object | The response payload. |
| `data.deleted` | boolean | Always true: the endpoint is gone once this returns. |
| `data.id` | string | Id of the deleted endpoint. |

```json
{
  "success": true,
  "data": {
    "deleted": true,
    "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
  }
}
```

### Errors

| Status | Code | Message |
| --- | --- | --- |
| 400 | `VALIDATION_ERROR` | Validation error |
| 401 | `UNAUTHORIZED` | API key required |
| 403 | `FORBIDDEN` | Valid key, not permitted |
| 404 | `NOT_FOUND` | Resource not found |
| 429 | `RATE_LIMITED` | Rate limit exceeded |
| 500 | `INTERNAL_ERROR` | Unexpected server error |

### Example

```bash
curl --request DELETE \
  --url 'https://api.gunspec.io/v1/me/webhooks/7c9e6679-7425-40de-944b-e07fc1f90ae7' \
  --header 'X-API-Key: your_key'
```

## client.webhooks.test()

`POST /v1/me/webhooks/{endpointId}/test`

Auth: API key required  
Tier: Explorer+

Send a signed test delivery, so a new endpoint can be verified before an event depends on it.

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `endpointId` | string | yes | Webhook endpoint id |

### Response 200 (application/json)

| Field | Type | Description |
| --- | --- | --- |
| `data` | object | The response payload. |
| `data.delivered` | boolean | Whether your endpoint answered with a 2xx. |
| `data.httpStatus` | integer \| null | The status your endpoint returned. Null when the request never got a response: DNS failure, timeout, TLS error. |
| `data.error` | string \| null | Why the calculation could not be completed, or null when it succeeded. |

```json
{
  "success": true,
  "data": {
    "delivered": true,
    "httpStatus": 200,
    "error": null
  }
}
```

### Errors

| Status | Code | Message |
| --- | --- | --- |
| 400 | `VALIDATION_ERROR` | Validation error |
| 401 | `UNAUTHORIZED` | API key required |
| 403 | `FORBIDDEN` | Valid key, not permitted |
| 404 | `NOT_FOUND` | Resource not found |
| 429 | `RATE_LIMITED` | Rate limit exceeded |
| 500 | `INTERNAL_ERROR` | Unexpected server error |

### Example

```bash
curl --request POST \
  --url 'https://api.gunspec.io/v1/me/webhooks/7c9e6679-7425-40de-944b-e07fc1f90ae7/test' \
  --header 'X-API-Key: your_key'
```
