Receive events with webhooks
Get a signed POST when an object version becomes searchable or fails to index: register a webhook, verify each event, handle retries, rotate the secret, and replay missed events.
Overview
A webhook is an HTTPS URL that Graphon calls when something happens in your workspace. Use one instead of polling. For example, learn when an object version becomes searchable.
- Graphon sends each event as a JSON
POST. - Graphon signs each POST with the Standard Webhooks scheme.
- Graphon retries a failed POST for about three days.
- Graphon keeps a delivery log for 30 days. You can replay events from it.
The Webhook events reference lists each event type and its fields. Manage webhooks with the API, the graphon webhooks CLI commands, or the MCP tools in the Webhooks toolset.
Personal and workspace webhooks
Webhooks have owners, like API keys.
| Personal webhook | Workspace webhook | |
|---|---|---|
| Who can create it | Any member, at any role | A workspace API key, at any role, or an Admin |
| Who can see, edit, and delete it | The member, and any Admin | Every workspace API key, and any Admin |
owner | me, the default for a member | workspace, the default for a workspace API key |
| When the member leaves or is removed | Graphon deletes it | It keeps working |
A webhook does not depend on the API key that created it. It keeps working after that key is revoked.
Choose events and buckets
eventTypeslists the event types to send. Leave it out, or sendnull, to get every type, including types that Graphon adds later.bucketIdslists the buckets whose events to send. Leave it out, or sendnull, to get every bucket, including buckets that you create later.
A new webhook gets only the events that happen after you create it.
Create a webhook
Graphon returns the signing secret once, in secret. Store it in a secret manager.
cURL
curl --request POST \
--url "https://api.beta.graphon.ai/v1/workspaces/$GRAPHON_WORKSPACE_ID/webhooks" \
--header "Authorization: Bearer $GRAPHON_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"name": "indexing-callbacks",
"url": "https://hooks.example.com/graphon",
"eventTypes": ["object.indexed", "object.indexing_failed"]
}'Response
{
"webhook": {
"id": "whkN4vB7qX2mZ",
"name": "indexing-callbacks",
"url": "https://hooks.example.com/graphon",
"eventTypes": ["object.indexed", "object.indexing_failed"],
"bucketIds": null,
"enabled": true,
"ownerKind": "user"
},
"secret": "whsec_<shown once>"
}CLI
# Writes the secret to .env as GRAPHON_WEBHOOK_SECRET instead of printing it.
graphon webhooks create --name indexing-callbacks \
--url https://hooks.example.com/graphon \
--event-type object.indexed --event-type object.indexing_failed \
--write-env .env
# Send one signed webhook.test event and show the answer.
graphon webhooks test <webhookId>The URL must follow these rules:
- It uses HTTPS.
- It holds no user name, password, or fragment. A query string and any port are fine.
- Its host resolves only to public addresses. Graphon checks this when you create or update the webhook, and again on each connection.
- It answers directly. Graphon does not follow redirects or use a proxy.
To check your receiver, send a test event with graphon webhooks test or the Test a webhook operation. The test event (webhook.test) goes once, with no retries, and the delivery log does not keep it. A test works while the webhook is off.
Verify each event
Each POST carries three signature headers:
| Header | Value |
|---|---|
webhook-id | The event ID. A retry and a replay send the same value. |
webhook-timestamp | When Graphon sent the POST, in Unix seconds. |
webhook-signature | v1, and a base64 HMAC-SHA256 signature. During a secret rotation, two signatures, separated by a space. |
Check them with a Standard Webhooks library. The library refuses a timestamp more than 5 minutes away from your clock, so a copied POST cannot be sent again later. Answer 400 to a POST that fails the check.
Install
npm install standardwebhooks # Node.js
pip install standardwebhooks # PythonNode.js
import express from 'express';
import { Webhook } from 'standardwebhooks';
const webhook = new Webhook(process.env.GRAPHON_WEBHOOK_SECRET);
const app = express();
// Keep the raw body. The signature covers the exact bytes that Graphon sent.
app.post('/graphon', express.raw({ type: 'application/json' }), (req, res) => {
let event;
try {
event = webhook.verify(req.body, req.headers);
} catch {
res.status(400).end();
return;
}
enqueue(event); // Your queue or background job.
res.status(204).end();
});
app.listen(3000);Python
import os
from flask import Flask, request
from standardwebhooks import Webhook, WebhookVerificationError
webhook = Webhook(os.environ["GRAPHON_WEBHOOK_SECRET"])
app = Flask(__name__)
@app.post("/graphon")
def graphon_event():
# Keep the raw body. The signature covers the exact bytes that Graphon sent.
try:
event = webhook.verify(request.get_data(), dict(request.headers))
except WebhookVerificationError:
return "", 400
enqueue(event) # Your queue or background job.
return "", 204Verify the raw body
Drop duplicates
Graphon delivers each event at least once. A retry, a replay, or a lost answer can send the same event again.
- Store the
webhook-idof each event that you handle. It equals the eventid. Skip an event whose ID you already have, and answer2xx. - Events can arrive in any order. Compare the event
timestampand the objectversionId, or read the current state from the API, when order matters. - Ignore an event type or a field that you do not know, and answer
2xx.
Answer fast
Graphon waits 15 seconds for an answer. No answer counts as a failure, and Graphon retries. Answer 2xx first, then do the work in a queue or a background job.
Retries
| Your answer | What Graphon does |
|---|---|
2xx | The delivery succeeded. |
429, 5xx, no answer in 15 seconds, a network error, or a TLS error | Retries later. |
3xx | The delivery failed. Graphon does not follow redirects. |
410 | The delivery failed, and Graphon turns the webhook off. |
| Any other 4xx | The delivery failed. |
- Graphon makes up to 10 attempts over about 75 hours. The retry schedule lists each wait.
- To slow Graphon down, answer
429with aRetry-Afterheader. When it asks for a longer wait than the schedule, Graphon waits that long, up to 24 hours. - Graphon also turns a webhook off when a delivery fails, the webhook is older than 72 hours, and it got no
2xxanswer in the last 72 hours. - A webhook that Graphon turned off shows
disabledReasonfailing.
To turn a webhook off yourself, update it with enabled: false. Its pending deliveries end as failed. To turn it on again, update it with enabled: true, then replay what it missed.
An off webhook records nothing
Rotate the secret
Rotating makes a new signing secret and shows it once. The previous secret keeps signing during an overlap, so you can deploy the new secret with no failed POSTs.
- The overlap is 24 hours by default. Set
previousSecretExpiresInSecondsfrom 0 to 604800 (7 days). 0 stops the previous secret at once. - During the overlap,
webhook-signatureholds both signatures. The library accepts a POST that matches either secret. - A second rotation during an overlap stops the oldest secret at once.
- The webhook shows
signing.secretFingerprint, not the secret, so you can tell which secret is current.
CLI
# The previous secret keeps signing for 24 hours unless you pass --previous-expires-in.
graphon webhooks rotate-secret <webhookId> --write-env .envRead the log and replay
The delivery log lists each event that the webhook got, newest first, with its status (pending, succeeded, or failed) and its last attempt. One delivery shows the exact body and every attempt.
CLI
graphon webhooks deliveries <webhookId> --status failed
graphon webhooks delivery <webhookId> <eventId>Replay sends past events again, with the same event ID and body, and a new timestamp and signature. Choose events by ID, or by time with since and until. onlyFailed skips deliveries that succeeded.
cURL
curl --request POST \
--url "https://api.beta.graphon.ai/v1/workspaces/$GRAPHON_WORKSPACE_ID/webhooks/<webhookId>/replay" \
--header "Authorization: Bearer $GRAPHON_API_KEY" \
--header "Content-Type: application/json" \
--data '{"since": "2026-10-13T00:00:00Z", "onlyFailed": true}'CLI
graphon webhooks replay <webhookId> --since 2026-10-13T00:00:00Z --only-failed
graphon webhooks replay <webhookId> --event <eventId> --event <eventId>- A replay starts a new round of up to 10 attempts.
- A delivery that is still pending stays queued once.
- A replay on a webhook that is off returns
409webhook_disabled. Turn the webhook on first. - Delete removes the webhook and its log. To stop deliveries and keep the log, turn the webhook off.
See also
The Webhook events reference covers each event body, the headers, and the retry schedule. The Create a webhook reference covers every webhook operation.