---
title: Test webhook
description: Send one signed webhook.test POST now, and return the receiver’s answer.
doc_version: 0.1.0-preview
last_updated: 2026-09-14
---

# Test webhook

`POST /webhooks/{webhook}:test`

Send one signed webhook.test POST now, and return the receiver’s answer.

**Permission:** Cluster key, or a scoped key with write on every collection that the webhook covers.

[API key types and authentication](/engine/reference/authentication.md).

The body is a batch of one webhook.test event, signed like any POST. A receiver can answer 2xx and do nothing.

It works while the webhook is disabled. It is not queued, never retried, and not in the delivery log.

A receiver that fails still gives 200: read attempt.status, response_status, and error. A 503 means that the POST did not go out within 15 seconds. Retry: a test changes nothing.

## Example request

```http
POST /webhooks/acme-status:test HTTP/1.1
Host: engine.example.com
Authorization: Bearer <GRAPHON_ENGINE_API_KEY>
```

## Path parameters

### `webhook`

**string · required**

Webhook ID with the whk_ prefix, or the webhook’s name.

## Responses

### 200 · Success

The receiver’s answer to the test POST. A receiver that fails is also a 200.

Content type: `application/json`.

#### Response headers

##### `x-engine-request-id`

**string · required**

The Engine request ID. Retain it when investigating a failed or unexpected request.

##### `x-request-id`

**string · required**

The X-Request-Id you sent, echoed when it is a valid ID. Otherwise, the Engine request ID.

```json
{
  "attempt": {
    "status": "succeeded",
    "response_status": 204,
    "error": null,
    "duration_ms": 87,
    "sent_at": "2026-10-06T10:00:00Z"
  }
}
```

#### Response fields

##### `attempt`

**object · required**

The one test POST and the receiver’s answer.

###### `attempt.duration_ms`

**integer · required**

Time from the start of the POST to the answer or the error, in milliseconds.

###### `attempt.error`

**string · required · nullable**

Why the POST got no usable answer, as for a delivery attempt. Null when the receiver answered with a status other than 3xx.

###### `attempt.response_status`

**integer · required · nullable**

HTTP status of the receiver’s answer. Null when no answer came back.

###### `attempt.sent_at`

**string (date-time) · required**

Time the test POST was sent, as an ISO 8601 UTC timestamp.

###### `attempt.status`

**string · required**

succeeded for a 2xx answer. failed for any other answer or an error.

- `succeeded`: The receiver answered 2xx.
- `failed`: The receiver answered with another status, or the POST got no answer.

## Errors

[Error format and retry guidance](/engine/reference/errors.md).

### 400 · `invalid_request`

A field, path, parameter, or conditional request rule is invalid.

Correct the request. Unknown fields are rejected.

```json
{
  "error": {
    "code": "invalid_request",
    "message": "A field, path, parameter, or conditional request rule is invalid.",
    "request_id": "req_example",
    "retryable": false,
    "details": {}
  }
}
```

### 401 · `unauthenticated`

The API key is missing, invalid, or revoked.

Send an active key in Authorization: Bearer or X-API-Key.

```json
{
  "error": {
    "code": "unauthenticated",
    "message": "The API key is missing, invalid, or revoked.",
    "request_id": "req_example",
    "retryable": false,
    "details": {}
  }
}
```

### 403 · `forbidden`

The key can see the resource but lacks permission for this operation.

Use a key with the required action or scope kind.

```json
{
  "error": {
    "code": "forbidden",
    "message": "The key can see the resource but lacks permission for this operation.",
    "request_id": "req_example",
    "retryable": false,
    "details": {}
  }
}
```

### 404 · `not_found`

The resource is absent, deleted, or outside this key’s scope.

Check the identifier and the key’s namespace and collection scope.

```json
{
  "error": {
    "code": "not_found",
    "message": "The resource is absent, deleted, or outside this key’s scope.",
    "request_id": "req_example",
    "retryable": false,
    "details": {}
  }
}
```

### 503 · `temporarily_unavailable`

A dependency, cluster readiness condition, or capacity limit prevents this operation.

Inspect retryable and details. Retry only when permitted, preserving the idempotency key for the same write.

```json
{
  "error": {
    "code": "temporarily_unavailable",
    "message": "A dependency, cluster readiness condition, or capacity limit prevents this operation.",
    "request_id": "req_example",
    "retryable": true,
    "details": {}
  }
}
```

### 500 · `internal_error`

An unexpected server failure prevented this request from completing.

Retain request_id for investigation. Follow retryable before retrying a write.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An unexpected server failure prevented this request from completing.",
    "request_id": "req_example",
    "retryable": false,
    "details": {}
  }
}
```

## Other request languages

### cURL

```bash
curl -sS -X POST "$GRAPHON_ENGINE_URL/webhooks/acme-status:test" \
  -H "Authorization: Bearer $GRAPHON_ENGINE_API_KEY"
```

### Python

```python
import json
import os
import urllib.request

request = urllib.request.Request(
    os.environ["GRAPHON_ENGINE_URL"] + "/webhooks/acme-status:test",
    headers={
        "Authorization": "Bearer " + os.environ["GRAPHON_ENGINE_API_KEY"],
    },
    method="POST",
)
with urllib.request.urlopen(request) as response:
    print(response.read().decode())
```

### JavaScript

```javascript
const response = await fetch(process.env.GRAPHON_ENGINE_URL + "/webhooks/acme-status:test", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.GRAPHON_ENGINE_API_KEY}`,
  },
});
console.log(response.status, await response.text());
```

## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
