Graphon Engine

Handle jobs and events

AI Draft

Verify completion events, deduplicate deliveries, and recover state.

View as Markdown

Use signed webhook events for live completion. Persist event IDs to handle duplicate deliveries, and read current resource state after a missed notification.

Configure your receiver

Create an HTTPS endpoint in your application. Ask the cluster operator to configure its URL, a unique name, and a signing secret. This webhook configuration is separate from API keys. There is no webhook CRUD endpoint.

Example operator configuration

events:
  webhooks:
    - name: acme-service
      url: https://app.example.com/engine-events
      secret: "<WEBHOOK_SIGNING_SECRET>"
      types:
        - job.updated
        - file.status_changed
        - collection.status_changed

The actual signing secret starts with whsec_. It belongs to this receiver and must not be an Engine API key. Omitting types subscribes to all supported event types; an empty list is invalid. The operator reloads configuration with a restart.

Call /info to check the webhook count and supported event types. Invalid receiver configuration makes /ready return 503. The maximum is eight configured receivers. A new receiver starts with new events and does not replay past events.

Connect a job to an event

1 · Request

202 Accepted

Retain file.id and job.id.

2 · Event

File ready

Verify the signature and event ID.

3 · Continue

Search or Query

Use the now-ready content.

Missed notification? Read the saved job or file once to recover current state.
Save the job and file IDs from the write response. Match a verified event to those IDs. Use a recovery GET if a notification is missed.

Save the job ID from each accepted write before updating your UI. Match job_id or file_id from the event to that operation. Use namespace and collection to route the status to the correct tenant.

Example event body

{
  "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",
  "data": {
    "status": "ready",
    "previous_status": "indexing"
  }
}

job.updated reports operation status. file.status_changed reports file readiness. collection.status_changed reports the collection summary. Initial job and file events have data.previous_status: null. A new collection in the empty state does not emit a creation event. Search and Query do not emit completion webhooks.

Verify before parsing

HeaderTypeMeaning

Content-Type

string

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

X-Graphon-Events-Signature

string

sha256= followed by the hex HMAC-SHA256 digest.

X-Graphon-Events-Timestamp

string containing Unix seconds

The delivery timestamp used in signature verification.

X-Graphon-Events-Delivery

string

A del_ identifier unique to this attempt at this webhook.

X-Graphon-Events-Webhook

string

The receiver’s configured name.

Select the receiver’s secret from trusted server configuration. Build the signed message from the timestamp header, a dot, and the exact raw body bytes. Compute HMAC-SHA256 and compare digests in constant time.

Reject a missing or malformed signature. Reject a timestamp more than 300 seconds before or after the current time. Re-encoding parsed JSON changes the signed bytes, so verify before parsing.

Python · verify one delivery

import hashlib
import hmac
import json
import time

def verify_event(raw_body: bytes, timestamp: str, signature: str, secret: str):
    if abs(time.time() - int(timestamp)) > 300:
        raise ValueError("Webhook timestamp is outside the allowed window.")
    message = timestamp.encode("ascii") + b"." + raw_body
    digest = hmac.new(secret.encode("utf-8"), message, hashlib.sha256).hexdigest()
    expected = "sha256=" + digest
    if not hmac.compare_digest(expected, signature):
        raise ValueError("Webhook signature does not match.")
    return json.loads(raw_body)

Call this function with the raw request body and the two signature headers. Reject conversion, decoding, and verification failures. The function verifies a delivery; your receiver must also deduplicate and authorize tenant access.

Deduplicate and acknowledge

Event id is stable across retries and receivers. The delivery header changes for each attempt. Store the event ID with the accepted work. If that ID already exists, acknowledge the duplicate without repeating its effects.

Accept the event into durable application storage or a queue before returning 2xx. Perform slower follow-up work after acceptance. Respond within 10 seconds. Apply your application’s access checks before showing a tenant’s event to a person.

A receiver can receive events from multiple namespaces. Filtering by namespace happens after signature verification. A valid signature identifies the configured cluster delivery; it does not sign a human into your application.

Handle retries

Receiver resultDelivery behavior

2xx

Delivery succeeds. Engine stops attempts for this event and receiver.

429 or 5xx

Engine retries this receiver.

Network error or timeout

Engine retries this receiver.

3xx

Delivery fails. Engine follows no redirect and does not retry.

Other 4xx

Delivery fails without a retry.

There are at most eight attempts: the original POST and seven retries. Retry delays are 1, 2, 4, 8, 16, 32, and 64 seconds. A failed receiver does not block delivery to another receiver.

Engine delivers events sequentially for each receiver. A retry delays later events for that receiver. After the final failed attempt, Engine stops delivering that event to that URL.

Recover after a missed event

Use the saved job ID for a one-time status read after a missed event, a receiver outage, or a reconnect. Do not use a GET loop as the live notification path.

GET https://engine.example.com/jobs/job_01J8Z3K4N5P6Q7R8S9T0V1W2X3

GET /jobs/job_01J8Z3K4N5P6Q7R8S9T0V1W2X3 HTTP/1.1
Host: engine.example.com
Authorization: Bearer <GRAPHON_ENGINE_API_KEY>

200 OK

{
  "id": "job_01J8Z3K4N5P6Q7R8S9T0V1W2X3",
  "type": "index_file",
  "status": "succeeded",
  "namespace": "acme",
  "collection": "incident-logs",
  "file_id": "fil_01J8Z3K4N5P6Q7R8S9T0V1W2X3",
  "error": null,
  "created_at": "2026-10-06T12:00:00Z",
  "updated_at": "2026-10-06T12:01:00Z"
}

A failed job includes error.code and error.message. Correct the request or source before another write. A succeeded job confirms completion. Read the file or collection if your UI needs its latest fields.

See Get job, Cancel job, and the events webhook contract.

Was this page helpful?