Graphon Engine

Events webhooks

Signed incoming status events, webhook scopes, and the delivery contract.

Graphon Engine sends status events as signed HTTP POST requests to the webhooks that keys register. This page defines those requests and their delivery behavior.

Webhooks and scopes

A key registers a webhook with Create webhook: a unique name, a scope, an HTTPS url, and optional types. The webhook gets each event in its scope whose type is in types. Null or omitted types selects every type, also types added later.

ScopeBodyGets the events of

cluster

{"kind": "cluster"}

Every namespace and collection. Only a cluster key can create or see it.

namespace

{"kind": "namespace", "namespaces": ["acme"]}

Every collection in the listed namespaces, also collections created later, and the namespace jobs. More than one namespace needs a cluster key.

collection

{"kind": "collection", "namespace": "acme", "collections": ["incident-logs"]}

The named collections in one namespace.

A new webhook gets only the events that occur after its create. Replay webhook deliveries queues earlier events while Engine still holds them (30 days). /info reports the number of webhooks that the calling key can see, and the supported event types.

The URL must use https, hold no user info or fragment, and resolve only to public addresses. Engine checks the address again on every connection, follows no redirect, and never reads the response body.

Engine takes no webhooks from the cluster configuration. A configuration that sets events.webhooks, also to an empty list, fails GET /ready with details.failed: ["events"].

Request headers

HeaderTypeContract

webhook-id

string

The batch ID, with the bat_ prefix. Every retry of the batch keeps it. A replay sends its events in a new batch with a new ID.

webhook-timestamp

string containing Unix seconds

The send time of this POST. Refuse a value more than 5 minutes from your clock.

webhook-signature

string

v1, and the base64 HMAC-SHA256 signature. During a secret rotation, two values separated by a space, the new secret first.

X-Graphon-Events-Webhook

string

The webhook’s name. One receiver URL can use it to pick the secret.

User-Agent

string

Graphon-Webhooks/1.

Content-Type

string

application/json. Verify the original body bytes before parsing.

The secret is whsec_ and the base64 of 32 random bytes. The HMAC key is the decoded bytes. The signed content is the exact UTF-8 bytes of webhook-id + "." + webhook-timestamp + ".", followed by the original HTTP body bytes. It covers the whole batch. Each signature is v1, and the base64 HMAC-SHA256 digest. During a rotation overlap, Engine signs with the new and the previous secret, newest first. Accept the POST when one signature matches.

Event body

POST to your webhook · JSON body

{
  "events": [
    {
      "id": "evt_01J8Z3K4N5P6Q7R8S9T0V1W2X3",
      "type": "file.status_changed",
      "occurred_at": "2026-10-06T12:01:00Z",
      "cluster": "graphon-engine",
      "namespace": "acme",
      "collection": "incident-logs",
      "job_id": "job_01J8Z3K4N5P6Q7R8S9T0V1W2X3",
      "file_id": "fil_01J8Z3K4N5P6Q7R8S9T0V1W2X3",
      "research_id": null,
      "data": {
        "status": "ready",
        "previous_status": "indexing"
      }
    }
  ]
}

The body is {"events": [...]}: the webhook’s oldest queued events, up to 500 events or 1 MiB. One larger event goes alone. Each event has the fields below.

FieldTypeContract

id

string · required

Stable evt_ ID for this status change. Use it to deduplicate.

type

string enum · required

job.updated, file.status_changed, file.stage_changed, collection.status_changed, research.updated, research.progress, or webhook.test.

occurred_at

UTC timestamp string · required

Time the resource status changed.

cluster

string · required

Cluster name reported by /info.

namespace

string or null · required

Namespace associated with the resource, or null when none applies.

collection

string or null · required

Collection associated with the resource, or null when none applies.

job_id

string or null · required

Related job_ ID, or null when the event has no related job.

file_id

string or null · required

Related fil_ ID, or null when the event has no related file.

research_id

string or null · required

Related rsr_ research run ID, or null for other event types.

data

object · required

Status transition payload.

data.status

string · required

New status for the resource identified by type.

data.previous_status

string or null · required

Status before the transition. Null on initial job, file, and research creation. A research.progress event has no status. A file.stage_changed event has previous_stage instead.

data.error

object · file.status_changed

Present when the status is failed: the failure code and message, as Get file reports them.

data.stage, data.previous_stage

string · file.stage_changed

The new stage of an indexing file, and the stage before it. previous_stage is null for a file that had no stage.

data.error_code

string · research.updated

The error code when the status is failed.

data.stage, data.label, data.step, data.total_steps

string or integer · research.progress

The new stage of a running research run.

data.job_type

string enum · optional

Operation type for a job event, such as index_file or apply_access_rule.

data.webhook

string · webhook.test

Name of the webhook under test.

File states are pending, indexing, ready, failed, and deleting. File stages, in order, are engine_queued, checking_source, core_queued, processing, and building_index. Only an indexing file has a stage. Job and research states are queued, running, succeeded, failed, and canceled. Collection states are empty, indexing, ready, degraded, failed, and deleting. Creating an empty collection does not emit a collection event.

webhook.test comes only from Test webhook. It is a batch of one event whose namespace, collection, and IDs are null. It is not queued, never retried, and not in the delivery log.

Test webhook · JSON body

{
  "events": [
    {
      "id": "evt_01J8Z3K4N5P6Q7R8S9T0V1W2X6",
      "type": "webhook.test",
      "occurred_at": "2026-10-06T12:00:00Z",
      "cluster": "graphon-engine",
      "namespace": null,
      "collection": null,
      "job_id": null,
      "file_id": null,
      "research_id": null,
      "data": {
        "webhook": "acme-status"
      }
    }
  ]
}

Within a major API version, new event types or status values can be added. Ignore an event type that you do not know, and answer 2xx. Preserve unknown values without treating them as a known success state.

Responses and delivery

Your responseEngine action

2xx

Every event in the batch succeeds. Engine sends the next batch.

429, 5xx, a network error, or no answer in 30 seconds

Engine sends the same batch again after 1, 2, 4, 8, 16, 32, and 64 seconds, then every 5 minutes, each ±10%. A longer Retry-After replaces the wait, up to 24 hours.

3xx

Every event in the batch fails. Engine follows no redirect. Then it sends the next batch.

410

Every event in the batch fails, and Engine turns the webhook off with disabled_reason failing.

Other 4xx

Every event in the batch fails. Then Engine sends the next batch.

Your response applies to the whole batch. Engine allows 30 seconds per POST. A retry sends the same bytes with the same webhook-id. A batch retries until the receiver answers or the webhook turns off. A failed event is not sent again unless a key replays it.

Engine sends one batch at a time to each webhook, in order. A retry delays only the later events of that webhook. One webhook’s failures never block another.

After a failed attempt, a webhook older than 72 hours with no 2xx in the last 72 hours turns off with disabled_reason failing. A 410 turns it off at once. Turning a webhook off ends its queued events failed. A key turns it on again with Update webhook, and the webhook then gets only new events.

Delivery log and replay

LevelWhat it isStatus

Delivery

One event queued at one webhook.

pending (queued, or waiting for a retry), succeeded, or failed.

Attempt

One POST of the batch that holds the event. Every delivery in the batch shares it. It keeps the HTTP status, an error code, and the duration, never a response body.

succeeded, retry, or failed.

Each webhook keeps 30 days of deliveries and attempts. A key with read on part of a wider webhook sees only the deliveries of its own namespace or collections. Replay queues past events again, by ID or by time range, when they are in the webhook’s scope and types and Engine still holds them. A replayed event keeps its id and its bytes, in a new batch. A replay on a disabled webhook returns 409 webhook_disabled.

Recovery

Use List webhook deliveries to find what a receiver missed, and Replay webhook deliveries to send it again. Current resource state also stays available through the resource GET endpoints. Use Get job, Get file, Get collection, or Get research for a gap older than 30 days.

The jobs and events guide includes receiver setup, verification code, and an acknowledgement workflow.

Was this page helpful?
View as Markdown