# Webhooks

Have events sent to your own URL: resources finishing or failing a change, invoices, payments and credit.

Source: https://docs.pantechdynamics.com/api/webhooks

A webhook is an `https` URL that Pantech `POST`s a signed JSON event to whenever something it subscribes to happens: an [operation](https://docs.pantechdynamics.com/api/operations) finishing or failing, an invoice, a payment, credit running low. Subscribe to exact types from `GET /webhook-event-types`, to one resource with `<resource>.*`, or to everything with `*`. The signing secret (`whsec_…`) is returned only when the webhook is created and when its secret is rotated.

Verify every delivery's `Pantech-Signature` before trusting it, and deduplicate on the event `id`: delivery is at least once and unordered. See the [webhooks guide](https://docs.pantechdynamics.com/webhooks/overview), [verifying signatures](https://docs.pantechdynamics.com/webhooks/verify-signatures) and the [event types](https://docs.pantechdynamics.com/webhooks/event-types).

Endpoints:

- `GET /public/v1/webhook-event-types`: List webhook event types
- `GET /public/v1/webhooks`: List webhooks
- `POST /public/v1/webhooks`: Create a webhook
- `GET /public/v1/webhooks/{webhook_id}`: Get a webhook
- `PATCH /public/v1/webhooks/{webhook_id}`: Change a webhook
- `DELETE /public/v1/webhooks/{webhook_id}`: Delete a webhook
- `POST /public/v1/webhooks/{webhook_id}/test`: Send a test event
- `POST /public/v1/webhooks/{webhook_id}/rotate-secret`: Rotate a webhook's secret
- `GET /public/v1/webhooks/{webhook_id}/deliveries`: List a webhook's deliveries
- `GET /public/v1/webhooks/{webhook_id}/deliveries/{delivery_id}`: Get a delivery
- `POST /public/v1/webhooks/{webhook_id}/deliveries/{delivery_id}/redeliver`: Send a delivery again

## The webhook object

A URL Pantech sends your organization's events to.

- `id` (string, required): e.g. `whk_...`.
- `url` (string, required): Where events are sent: an `https` URL on the public internet.
- `description` (string, required): Your note about it; may be empty.
- `events` (array of string, required): What it subscribes to: event types, `<resource>.*`, or `*`.
- `enabled` (boolean, required): Whether events are sent to it.
- `disabled_reason` (string or null, required): Why it is disabled: `disabled_by_user`, or `failing` after three days of failed deliveries. Null while enabled. One of `disabled_by_user`, `failing`, `null`.
- `failing_since` (string or null, required): When its deliveries started failing with no success since (RFC 3339, UTC); null while they succeed.
- `created_at` (string, required): When it was created (RFC 3339, UTC).
- `updated_at` (string, required): When it last changed (RFC 3339, UTC).
## The webhook delivery object

One event sent, or waiting to be sent, to one webhook.

- `id` (string, required): e.g. `whd_...`.
- `webhook_id` (string, required): The webhook it is for.
- `event_id` (string, required): The event's `id`, the same in every delivery of it: deduplicate on it.
- `event_type` (string, required): e.g. `instance.created`.
- `status` (string, required): `pending` until it succeeds, fails eight times, or is cancelled because the webhook was disabled or deleted. One of `pending`, `succeeded`, `failed`, `canceled`.
- `attempts` (integer, required): How many times it has been sent.
- `next_attempt_at` (string or null, required): When it is next sent, while `pending`.
- `last_attempt_at` (string or null, required): When it was last sent.
- `last_status_code` (integer or null, required): The HTTP status your endpoint last answered; null if it did not answer.
- `last_error` (string or null, required): Why the last attempt failed: `http_<status>`, `timeout`, `connection_failed`, `dns_failed`, `tls_failed`, `blocked_address` (the URL resolved to an address that is not on the public internet), `invalid_url`, `endpoint_disabled` or `endpoint_deleted`.
- `last_response` (string or null, required): The first 1,024 characters of your endpoint's last response body.
- `last_duration_ms` (integer or null, required): How long the last attempt took, in milliseconds.
- `delivered_at` (string or null, required): When it succeeded.
- `created_at` (string, required): When the event was queued for this webhook.
## The webhook event object

The body of every delivery. An infrastructure event's `data` names the resource and the change: `resource_type`, `resource_id`, `operation_id`, `action`, `status` (`succeeded` or `failed`) and, on failure, `error` with the same safe `code` and `message` the operations API returns. Read the resource through the API for more. A billing event's `data` carries its amounts in minor units.

- `id` (string): The event's id (`evt_…`). Deduplicate on it.
- `type` (string): e.g. `instance.created`, `invoice.paid`, `webhook.test`.
- `api_version` (string): The shape of this body, as a date: `2026-10-22`.
- `created_at` (string): When it happened (RFC 3339, UTC).
- `organization_id` (string): Your organization.
- `project_id` (string): The project (`acc_…`) the resource is in.
- `data` (object): What happened; depends on `type`.
## List webhook event types

```http
GET /public/v1/webhook-event-types
```

Every event type a webhook can subscribe to, with what it means. Subscribe to an exact type, to every type of one resource with `<resource>.*` (for example `instance.*`), or to everything with `*`.

Infrastructure events are named `<resource>.<past tense>` when a change finished (`instance.created`, `volume.attached`) and `<resource>.<verb>_failed` when it failed (`instance.create_failed`).

Scope: `read`.

Example:

```sh
curl "https://api.pantechdynamics.com/public/v1/webhook-event-types" \
  -H "Authorization: Bearer $PANTECH_API_KEY"
```

Response `200`:

```json
{
  "data": [
    {
      "type": "instance.created",
      "resource": "instance",
      "description": "Instance: create finished."
    }
  ],
  "next_cursor": null
}
```

Errors:

- `401`: `UNAUTHENTICATED`: the request has no API key, or the key is mistyped, revoked or expired, or its creator has left the organization. Send `Authorization: Bearer PAN_…` with a valid key; create one in the console under Organization › API keys.
- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `429`: `RATE_LIMITED`: this API key, verified user or organization used its request budget for the current window, or this address exceeded an IP/failed-auth limit. Wait `Retry-After` seconds, then retry. For authenticated quota denials, `RateLimit-Scope` identifies the denying quota and `RateLimit-Reset` reports its remaining window in seconds.
- `503`: `WEBHOOKS_UNAVAILABLE`: webhooks are not available on this deployment yet. Try again later.
`SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
## List webhooks

```http
GET /public/v1/webhooks
```

Your organization's webhooks: the URLs Pantech sends events to. The signing secret is never part of a webhook; it is shown once, when the webhook is created and when its secret is rotated.

Scope: `read`.

Example:

```sh
curl "https://api.pantechdynamics.com/public/v1/webhooks" \
  -H "Authorization: Bearer $PANTECH_API_KEY"
```

Response `200`:

```json
{
  "data": [
    {
      "id": "whk_06gk3x9tq7m2v8r4y1c5d0zp8w",
      "url": "https://example.com/hooks/pantech",
      "description": "Production alerts",
      "events": [
        "instance.*",
        "invoice.paid"
      ],
      "enabled": true,
      "disabled_reason": null,
      "failing_since": null,
      "created_at": "2026-10-03T09:30:00Z",
      "updated_at": "2026-10-03T09:30:00Z"
    }
  ],
  "next_cursor": null
}
```

Errors:

- `401`: `UNAUTHENTICATED`: the request has no API key, or the key is mistyped, revoked or expired, or its creator has left the organization. Send `Authorization: Bearer PAN_…` with a valid key; create one in the console under Organization › API keys.
- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `429`: `RATE_LIMITED`: this API key, verified user or organization used its request budget for the current window, or this address exceeded an IP/failed-auth limit. Wait `Retry-After` seconds, then retry. For authenticated quota denials, `RateLimit-Scope` identifies the denying quota and `RateLimit-Reset` reports its remaining window in seconds.
- `503`: `WEBHOOKS_UNAVAILABLE`: webhooks are not available on this deployment yet. Try again later.
`SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
## Create a webhook

```http
POST /public/v1/webhooks
```

Adds a URL Pantech sends events to. Every delivery is a JSON `POST` signed with the webhook's secret, which is in this response only: store it now.

**Verifying a delivery.** Each request carries `Pantech-Signature: t=<unix seconds>,v1=<hex>`. Compute HMAC-SHA256 of `<t>.<raw body>` with the secret and compare it, in constant time, with any `v1` value; reject a `t` more than five minutes old. During the 24 hours after a rotation there are two `v1` values, one per secret. `Pantech-Event-Id`, `Pantech-Event-Type` and `Pantech-Delivery-Id` name what was sent.

**Delivery.** Any `2xx` within 10 seconds is success. Anything else, including a redirect, is retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours and 10 hours, then marked failed. A webhook that has failed for three days without a single success is disabled. Events can arrive more than once and out of order: deduplicate on the event `id`.

At most 10 webhooks per organization. Owners and admins only.

**Idempotency:** a retry with the same `Idempotency-Key` returns the webhook the first request made, with `secret: null`: the secret is never shown again. The same key with a different request returns `409 IDEMPOTENCY_CONFLICT`.

Scope: `write`. Send an `Idempotency-Key` header.

Body:

- `url` (string, required): An `https` URL on the public internet, at most 2,048 characters, with no user name or password.
- `description` (string): A note for yourself, at most 255 characters.
- `events` (array of string, required): 1 to 100 entries: event types from `GET /webhook-event-types`, `<resource>.*`, or `*`.

Example:

```sh
curl -X POST "https://api.pantechdynamics.com/public/v1/webhooks" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/hooks/pantech",
    "events": [
      "instance.*",
      "invoice.paid"
    ]
  }'
```

Response `201`:

```json
{
  "id": "whk_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "url": "https://example.com/hooks/pantech",
  "description": "Production alerts",
  "events": [
    "instance.*",
    "invoice.paid"
  ],
  "enabled": true,
  "disabled_reason": null,
  "failing_since": null,
  "created_at": "2026-10-03T09:30:00Z",
  "updated_at": "2026-10-03T09:30:00Z",
  "secret": "whsec_Jx2Qm9vR4tY7wK1pZ8nB3cF6hL0sD5gA2eU9iO4rT7y"
}
```

Errors:

- `400`: `IDEMPOTENCY_KEY_REQUIRED`: the `Idempotency-Key` header is missing. Send a new unique value, such as a UUID, with every write.
`INVALID_IDEMPOTENCY_KEY`: the `Idempotency-Key` header is longer than 255 characters. Use a shorter value.
`INVALID_REQUEST_BODY`: the body is not a single valid JSON object. Send one JSON object with the fields documented for this endpoint.
- `401`: `UNAUTHENTICATED`: the request has no API key, or the key is mistyped, revoked or expired, or its creator has left the organization. Send `Authorization: Bearer PAN_…` with a valid key; create one in the console under Organization › API keys.
- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the `write` scope. Use a key with that scope.
`FORBIDDEN`: the member who created the key is not an owner or admin. Use a key created by one.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server. An owner can turn it on in the console under Organization › AI apps.
`WEBHOOK_LIMIT_REACHED`: your organization already has 10 webhooks. Delete one first.
- `409`: `IDEMPOTENCY_CONFLICT`: this `Idempotency-Key` was already used for a different webhook. Use a new key.
- `413`: `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
- `422`: `VALIDATION_FAILED`: a field is invalid. Each entry of `errors` names the `field` and its `code`:
`url` `REQUIRED` or `INVALID_WEBHOOK_URL`: send an `https` URL on the public internet, with no user name or password, of at most 2,048 characters.
`events` `INVALID_WEBHOOK_EVENTS`: send 1 to 100 entries, each an event type from `GET /webhook-event-types`, `<resource>.*`, or `*`.
`description` `INVALID_DESCRIPTION`: send at most 255 characters.
`UNKNOWN_FIELD`: the body has a field this endpoint does not take (named in `field`). Remove it or fix its spelling.
- `429`: `RATE_LIMITED`: this API key, verified user or organization used its request budget for the current window, or this address exceeded an IP/failed-auth limit. Wait `Retry-After` seconds, then retry. For authenticated quota denials, `RateLimit-Scope` identifies the denying quota and `RateLimit-Reset` reports its remaining window in seconds.
- `503`: `WEBHOOKS_UNAVAILABLE`: webhooks are not available on this deployment yet. Try again later.
`SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
## Get a webhook

```http
GET /public/v1/webhooks/{webhook_id}
```

One webhook. `failing_since` is when its deliveries started failing without a success since; null while they succeed.

Scope: `read`.

Path parameters:

- `webhook_id`: The webhook's id (`whk_…`).

Example:

```sh
curl "https://api.pantechdynamics.com/public/v1/webhooks/whk_06gk3x9tq7m2v8r4y1c5d0zp8w" \
  -H "Authorization: Bearer $PANTECH_API_KEY"
```

Response `200`:

```json
{
  "id": "whk_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "url": "https://example.com/hooks/pantech",
  "description": "Production alerts",
  "events": [
    "instance.*",
    "invoice.paid"
  ],
  "enabled": true,
  "disabled_reason": null,
  "failing_since": null,
  "created_at": "2026-10-03T09:30:00Z",
  "updated_at": "2026-10-03T09:30:00Z"
}
```

Errors:

- `401`: `UNAUTHENTICATED`: the request has no API key, or the key is mistyped, revoked or expired, or its creator has left the organization. Send `Authorization: Bearer PAN_…` with a valid key; create one in the console under Organization › API keys.
- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `404`: `WEBHOOK_NOT_FOUND`: no webhook with this id in your organization. List your webhooks to find it.
`RESOURCE_NOT_FOUND`: the id is not a webhook id (`whk_…`). Check the id.
- `429`: `RATE_LIMITED`: this API key, verified user or organization used its request budget for the current window, or this address exceeded an IP/failed-auth limit. Wait `Retry-After` seconds, then retry. For authenticated quota denials, `RateLimit-Scope` identifies the denying quota and `RateLimit-Reset` reports its remaining window in seconds.
- `503`: `WEBHOOKS_UNAVAILABLE`: webhooks are not available on this deployment yet. Try again later.
`SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
## Change a webhook

```http
PATCH /public/v1/webhooks/{webhook_id}
```

Changes the URL, description, events or `enabled`; fields you leave out keep their value. Enabling a disabled webhook clears its failure history. Deliveries still waiting for a disabled webhook are cancelled when they come due.

**Idempotency:** the same request applied twice leaves the webhook the same.

Scope: `write`. Send an `Idempotency-Key` header.

Path parameters:

- `webhook_id`: The webhook's id (`whk_…`).

Body:

- `url` (string): A new `https` URL.
- `description` (string): A new note, at most 255 characters.
- `events` (array of string): The new subscriptions, replacing the old ones.
- `enabled` (boolean): `false` stops deliveries; `true` resumes them and clears the failure history.

Example:

```sh
curl -X PATCH "https://api.pantechdynamics.com/public/v1/webhooks/whk_06gk3x9tq7m2v8r4y1c5d0zp8w" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "enabled": true
  }'
```

Response `200`:

```json
{
  "id": "whk_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "url": "https://example.com/hooks/pantech",
  "description": "Production alerts",
  "events": [
    "instance.*",
    "invoice.paid"
  ],
  "enabled": true,
  "disabled_reason": null,
  "failing_since": null,
  "created_at": "2026-10-03T09:30:00Z",
  "updated_at": "2026-10-03T09:30:00Z"
}
```

Errors:

- `400`: `IDEMPOTENCY_KEY_REQUIRED`: the `Idempotency-Key` header is missing. Send a new unique value, such as a UUID, with every write.
`INVALID_IDEMPOTENCY_KEY`: the `Idempotency-Key` header is longer than 255 characters. Use a shorter value.
`INVALID_REQUEST_BODY`: the body is not a single valid JSON object. Send one JSON object with the fields documented for this endpoint.
- `401`: `UNAUTHENTICATED`: the request has no API key, or the key is mistyped, revoked or expired, or its creator has left the organization. Send `Authorization: Bearer PAN_…` with a valid key; create one in the console under Organization › API keys.
- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `404`: `WEBHOOK_NOT_FOUND`: no webhook with this id in your organization. List your webhooks to find it.
`RESOURCE_NOT_FOUND`: the id is not a webhook id (`whk_…`). Check the id.
- `409`: `CONFLICT`: the webhook was changed at the same time. Get it again and retry.
- `413`: `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
- `422`: `VALIDATION_FAILED`: a field is invalid. Each entry of `errors` names the `field` and its `code`:
`url` `REQUIRED` or `INVALID_WEBHOOK_URL`: send an `https` URL on the public internet, with no user name or password, of at most 2,048 characters.
`events` `INVALID_WEBHOOK_EVENTS`: send 1 to 100 entries, each an event type from `GET /webhook-event-types`, `<resource>.*`, or `*`.
`description` `INVALID_DESCRIPTION`: send at most 255 characters.
`UNKNOWN_FIELD`: the body has a field this endpoint does not take (named in `field`). Remove it or fix its spelling.
- `429`: `RATE_LIMITED`: this API key, verified user or organization used its request budget for the current window, or this address exceeded an IP/failed-auth limit. Wait `Retry-After` seconds, then retry. For authenticated quota denials, `RateLimit-Scope` identifies the denying quota and `RateLimit-Reset` reports its remaining window in seconds.
- `503`: `WEBHOOKS_UNAVAILABLE`: webhooks are not available on this deployment yet. Try again later.
`SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
## Delete a webhook

```http
DELETE /public/v1/webhooks/{webhook_id}
```

Deletes a webhook and its delivery history. Nothing more is sent to it.

**Idempotency:** a retry is not replayed. Once the webhook is deleted, a repeat returns `404`; treat that as done.

**Scope:** `write`. Destructive: an AI app's key also needs `delete`.

Scope: `write`. Send an `Idempotency-Key` header.

Path parameters:

- `webhook_id`: The webhook's id (`whk_…`).

Example:

```sh
curl -X DELETE "https://api.pantechdynamics.com/public/v1/webhooks/whk_06gk3x9tq7m2v8r4y1c5d0zp8w" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
```

Response `204`: Deleted.

Errors:

- `400`: `IDEMPOTENCY_KEY_REQUIRED`: the `Idempotency-Key` header is missing. Send a new unique value, such as a UUID, with every write.
`INVALID_IDEMPOTENCY_KEY`: the `Idempotency-Key` header is longer than 255 characters. Use a shorter value.
- `401`: `UNAUTHENTICATED`: the request has no API key, or the key is mistyped, revoked or expired, or its creator has left the organization. Send `Authorization: Bearer PAN_…` with a valid key; create one in the console under Organization › API keys.
- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `404`: `WEBHOOK_NOT_FOUND`: no webhook with this id in your organization. List your webhooks to find it.
`RESOURCE_NOT_FOUND`: the id is not a webhook id (`whk_…`). Check the id.
- `429`: `RATE_LIMITED`: this API key, verified user or organization used its request budget for the current window, or this address exceeded an IP/failed-auth limit. Wait `Retry-After` seconds, then retry. For authenticated quota denials, `RateLimit-Scope` identifies the denying quota and `RateLimit-Reset` reports its remaining window in seconds.
- `503`: `WEBHOOKS_UNAVAILABLE`: webhooks are not available on this deployment yet. Try again later.
`SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
## Send a test event

```http
POST /public/v1/webhooks/{webhook_id}/test
```

Queues a `webhook.test` event to this webhook, whatever it subscribes to, and returns the delivery. It is sent within a few seconds and retried like any other.

**Idempotency:** a retry is not replayed: each request sends another test event.

Scope: `write`. Send an `Idempotency-Key` header.

Path parameters:

- `webhook_id`: The webhook's id (`whk_…`).

Example:

```sh
curl -X POST "https://api.pantechdynamics.com/public/v1/webhooks/whk_06gk3x9tq7m2v8r4y1c5d0zp8w/test" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
```

Response `202`:

```json
{
  "id": "whd_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "webhook_id": "whk_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "event_id": "evt_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "event_type": "webhook.test",
  "status": "pending",
  "attempts": 0,
  "next_attempt_at": "2026-10-03T09:30:00Z",
  "last_attempt_at": null,
  "last_status_code": null,
  "last_error": null,
  "last_response": null,
  "last_duration_ms": null,
  "delivered_at": null,
  "created_at": "2026-10-03T09:30:00Z",
  "payload": {
    "id": "evt_06gk3x9tq7m2v8r4y1c5d0zp8w",
    "type": "webhook.test",
    "api_version": "2026-10-22",
    "created_at": "2026-10-03T09:30:00Z",
    "organization_id": "org_06gk3x9tq7m2v8r4y1c5d0zp8w",
    "project_id": "acc_06gk3x9tq7m2v8r4y1c5d0zp8w",
    "data": {
      "endpoint_id": "whk_06gk3x9tq7m2v8r4y1c5d0zp8w",
      "message": "This is a test event from Pantech."
    }
  }
}
```

Errors:

- `400`: `IDEMPOTENCY_KEY_REQUIRED`: the `Idempotency-Key` header is missing. Send a new unique value, such as a UUID, with every write.
`INVALID_IDEMPOTENCY_KEY`: the `Idempotency-Key` header is longer than 255 characters. Use a shorter value.
- `401`: `UNAUTHENTICATED`: the request has no API key, or the key is mistyped, revoked or expired, or its creator has left the organization. Send `Authorization: Bearer PAN_…` with a valid key; create one in the console under Organization › API keys.
- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `404`: `WEBHOOK_NOT_FOUND`: no webhook with this id in your organization. List your webhooks to find it.
`RESOURCE_NOT_FOUND`: the id is not a webhook id (`whk_…`). Check the id.
- `409`: `WEBHOOK_DISABLED`: the webhook is disabled. Enable it first.
- `429`: `RATE_LIMITED`: this API key, verified user or organization used its request budget for the current window, or this address exceeded an IP/failed-auth limit. Wait `Retry-After` seconds, then retry. For authenticated quota denials, `RateLimit-Scope` identifies the denying quota and `RateLimit-Reset` reports its remaining window in seconds.
- `503`: `WEBHOOKS_UNAVAILABLE`: webhooks are not available on this deployment yet. Try again later.
`SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
## Rotate a webhook's secret

```http
POST /public/v1/webhooks/{webhook_id}/rotate-secret
```

Replaces the signing secret, which is in this response only. For 24 hours every delivery is signed with both the new and the old secret, so you can update your receiver without dropping events.

**Idempotency:** a retry is not replayed: each request rotates again, and only the latest secret and the one before it sign.

Scope: `write`. Send an `Idempotency-Key` header.

Path parameters:

- `webhook_id`: The webhook's id (`whk_…`).

Example:

```sh
curl -X POST "https://api.pantechdynamics.com/public/v1/webhooks/whk_06gk3x9tq7m2v8r4y1c5d0zp8w/rotate-secret" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
```

Response `200`:

```json
{
  "id": "whk_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "url": "https://example.com/hooks/pantech",
  "description": "Production alerts",
  "events": [
    "instance.*",
    "invoice.paid"
  ],
  "enabled": true,
  "disabled_reason": null,
  "failing_since": null,
  "created_at": "2026-10-03T09:30:00Z",
  "updated_at": "2026-10-03T09:30:00Z",
  "secret": "whsec_Jx2Qm9vR4tY7wK1pZ8nB3cF6hL0sD5gA2eU9iO4rT7y"
}
```

Errors:

- `400`: `IDEMPOTENCY_KEY_REQUIRED`: the `Idempotency-Key` header is missing. Send a new unique value, such as a UUID, with every write.
`INVALID_IDEMPOTENCY_KEY`: the `Idempotency-Key` header is longer than 255 characters. Use a shorter value.
- `401`: `UNAUTHENTICATED`: the request has no API key, or the key is mistyped, revoked or expired, or its creator has left the organization. Send `Authorization: Bearer PAN_…` with a valid key; create one in the console under Organization › API keys.
- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `404`: `WEBHOOK_NOT_FOUND`: no webhook with this id in your organization. List your webhooks to find it.
`RESOURCE_NOT_FOUND`: the id is not a webhook id (`whk_…`). Check the id.
- `409`: `CONFLICT`: the webhook was changed at the same time. Retry.
- `429`: `RATE_LIMITED`: this API key, verified user or organization used its request budget for the current window, or this address exceeded an IP/failed-auth limit. Wait `Retry-After` seconds, then retry. For authenticated quota denials, `RateLimit-Scope` identifies the denying quota and `RateLimit-Reset` reports its remaining window in seconds.
- `503`: `WEBHOOKS_UNAVAILABLE`: webhooks are not available on this deployment yet. Try again later.
`SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
## List a webhook's deliveries

```http
GET /public/v1/webhooks/{webhook_id}/deliveries
```

Every event sent or waiting to be sent to this webhook, newest first, kept for 30 days: its status, attempts, and how the last attempt went.

Scope: `read`.

Path parameters:

- `webhook_id`: The webhook's id (`whk_…`).

Query parameters:

- `limit` (integer): Items per page, 1 to 100. Defaults to 50. From 1 to 100. Defaults to `50`.
- `cursor` (string): The previous page's `next_cursor`, to get the next page. Leave out for the first page; keep the other parameters the same.

Example:

```sh
curl "https://api.pantechdynamics.com/public/v1/webhooks/whk_06gk3x9tq7m2v8r4y1c5d0zp8w/deliveries" \
  -H "Authorization: Bearer $PANTECH_API_KEY"
```

Response `200`:

```json
{
  "data": [
    {
      "id": "whd_06gk3x9tq7m2v8r4y1c5d0zp8w",
      "webhook_id": "whk_06gk3x9tq7m2v8r4y1c5d0zp8w",
      "event_id": "evt_06gk3x9tq7m2v8r4y1c5d0zp8w",
      "event_type": "instance.created",
      "status": "succeeded",
      "attempts": 1,
      "next_attempt_at": null,
      "last_attempt_at": "2026-10-03T09:30:00Z",
      "last_status_code": 200,
      "last_error": null,
      "last_response": "ok",
      "last_duration_ms": 184,
      "delivered_at": "2026-10-03T09:30:00Z",
      "created_at": "2026-10-03T09:30:00Z"
    }
  ],
  "next_cursor": "eyJhdCI6IjIwMjYtMTAtMDNUMDk6MzA6MDBaIiwiaWQiOiJ2bV8wNmdrIn0"
}
```

Errors:

- `400`: `INVALID_PAGE`: `limit` is not an integer from 1 to 100.
`INVALID_CURSOR`: the cursor is not valid for this list. Start again without one.
- `401`: `UNAUTHENTICATED`: the request has no API key, or the key is mistyped, revoked or expired, or its creator has left the organization. Send `Authorization: Bearer PAN_…` with a valid key; create one in the console under Organization › API keys.
- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `404`: `WEBHOOK_NOT_FOUND`: no webhook with this id in your organization. List your webhooks to find it.
`RESOURCE_NOT_FOUND`: the id is not a webhook id (`whk_…`). Check the id.
- `429`: `RATE_LIMITED`: this API key, verified user or organization used its request budget for the current window, or this address exceeded an IP/failed-auth limit. Wait `Retry-After` seconds, then retry. For authenticated quota denials, `RateLimit-Scope` identifies the denying quota and `RateLimit-Reset` reports its remaining window in seconds.
- `503`: `WEBHOOKS_UNAVAILABLE`: webhooks are not available on this deployment yet. Try again later.
`SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
## Get a delivery

```http
GET /public/v1/webhooks/{webhook_id}/deliveries/{delivery_id}
```

One delivery, with `payload`: the exact body sent.

Scope: `read`.

Path parameters:

- `webhook_id`: The webhook's id (`whk_…`).
- `delivery_id`: The delivery's id (`whd_…`).

Example:

```sh
curl "https://api.pantechdynamics.com/public/v1/webhooks/whk_06gk3x9tq7m2v8r4y1c5d0zp8w/deliveries/whd_06gk3x9tq7m2v8r4y1c5d0zp8w" \
  -H "Authorization: Bearer $PANTECH_API_KEY"
```

Response `200`:

```json
{
  "id": "whd_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "webhook_id": "whk_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "event_id": "evt_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "event_type": "instance.created",
  "status": "succeeded",
  "attempts": 1,
  "next_attempt_at": null,
  "last_attempt_at": "2026-10-03T09:30:00Z",
  "last_status_code": 200,
  "last_error": null,
  "last_response": "ok",
  "last_duration_ms": 184,
  "delivered_at": "2026-10-03T09:30:00Z",
  "created_at": "2026-10-03T09:30:00Z",
  "payload": {
    "id": "evt_06gk3x9tq7m2v8r4y1c5d0zp8w",
    "type": "instance.created",
    "api_version": "2026-10-22",
    "created_at": "2026-10-03T09:30:00Z",
    "organization_id": "org_06gk3x9tq7m2v8r4y1c5d0zp8w",
    "project_id": "acc_06gk3x9tq7m2v8r4y1c5d0zp8w",
    "data": {
      "resource_type": "instance",
      "resource_id": "vm_06gk3x9tq7m2v8r4y1c5d0zp8w",
      "operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w",
      "action": "create",
      "status": "succeeded"
    }
  }
}
```

Errors:

- `401`: `UNAUTHENTICATED`: the request has no API key, or the key is mistyped, revoked or expired, or its creator has left the organization. Send `Authorization: Bearer PAN_…` with a valid key; create one in the console under Organization › API keys.
- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `404`: `WEBHOOK_NOT_FOUND`: no webhook with this id in your organization. List your webhooks to find it.
`WEBHOOK_DELIVERY_NOT_FOUND`: no delivery with this id for this webhook.
`RESOURCE_NOT_FOUND`: an id is not a webhook id (`whk_…`) or delivery id (`whd_…`). Check the ids.
- `429`: `RATE_LIMITED`: this API key, verified user or organization used its request budget for the current window, or this address exceeded an IP/failed-auth limit. Wait `Retry-After` seconds, then retry. For authenticated quota denials, `RateLimit-Scope` identifies the denying quota and `RateLimit-Reset` reports its remaining window in seconds.
- `503`: `WEBHOOKS_UNAVAILABLE`: webhooks are not available on this deployment yet. Try again later.
`SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
## Send a delivery again

```http
POST /public/v1/webhooks/{webhook_id}/deliveries/{delivery_id}/redeliver
```

Queues a delivery that succeeded, failed or was cancelled to be sent again from its first attempt, with the same event `id`.

**Idempotency:** a retry while it is being sent returns `409 WEBHOOK_DELIVERY_IN_PROGRESS`; treat that as done.

Scope: `write`. Send an `Idempotency-Key` header.

Path parameters:

- `webhook_id`: The webhook's id (`whk_…`).
- `delivery_id`: The delivery's id (`whd_…`).

Example:

```sh
curl -X POST "https://api.pantechdynamics.com/public/v1/webhooks/whk_06gk3x9tq7m2v8r4y1c5d0zp8w/deliveries/whd_06gk3x9tq7m2v8r4y1c5d0zp8w/redeliver" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
```

Response `202`:

```json
{
  "id": "whd_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "webhook_id": "whk_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "event_id": "evt_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "event_type": "instance.created",
  "status": "succeeded",
  "attempts": 1,
  "next_attempt_at": null,
  "last_attempt_at": "2026-10-03T09:30:00Z",
  "last_status_code": 200,
  "last_error": null,
  "last_response": "ok",
  "last_duration_ms": 184,
  "delivered_at": "2026-10-03T09:30:00Z",
  "created_at": "2026-10-03T09:30:00Z"
}
```

Errors:

- `400`: `IDEMPOTENCY_KEY_REQUIRED`: the `Idempotency-Key` header is missing. Send a new unique value, such as a UUID, with every write.
`INVALID_IDEMPOTENCY_KEY`: the `Idempotency-Key` header is longer than 255 characters. Use a shorter value.
- `401`: `UNAUTHENTICATED`: the request has no API key, or the key is mistyped, revoked or expired, or its creator has left the organization. Send `Authorization: Bearer PAN_…` with a valid key; create one in the console under Organization › API keys.
- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `404`: `WEBHOOK_NOT_FOUND`: no webhook with this id in your organization. List your webhooks to find it.
`WEBHOOK_DELIVERY_NOT_FOUND`: no delivery with this id for this webhook.
`RESOURCE_NOT_FOUND`: an id is not a webhook id (`whk_…`) or delivery id (`whd_…`). Check the ids.
- `409`: `WEBHOOK_DISABLED`: the webhook is disabled. Enable it first.
`WEBHOOK_DELIVERY_IN_PROGRESS`: the delivery is still being attempted. Wait for it to finish.
- `429`: `RATE_LIMITED`: this API key, verified user or organization used its request budget for the current window, or this address exceeded an IP/failed-auth limit. Wait `Retry-After` seconds, then retry. For authenticated quota denials, `RateLimit-Scope` identifies the denying quota and `RateLimit-Reset` reports its remaining window in seconds.
- `503`: `WEBHOOKS_UNAVAILABLE`: webhooks are not available on this deployment yet. Try again later.
`SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
