Webhooks
Webhooks deliver events to your server as they happen — no polling required.
The Xavigate webhook system implements the Standard Webhooks specification. All events are signed with HMAC-SHA256.
Setup
- In your dashboard, go to Webhooks → Add endpoint
- Enter your URL (must be HTTPS in production; HTTP allowed in sandbox)
- Select which events to receive
- Copy the per-endpoint signing secret
You can register multiple endpoints. Each has its own signing secret.
Events
| Event | When |
|---|---|
nature.assessment.completed | Scoring finished; profile is ready |
nature.profile.updated | A subject's profile was updated |
Event payload
{
"type": "nature.assessment.completed",
"data": {
"assessment_id": "asm_xyz789",
"subject_id": "sub_abc123",
"profile_id": "prf_qrs456",
"livemode": false
},
"created_at": "2026-04-21T10:35:00Z",
"webhook_id": "evt_aaa111"
}
Verifying signatures
Every webhook POST includes Standard Webhooks signing headers:
webhook-id: evt_aaa111
webhook-timestamp: 1713693300
webhook-signature: v1,base64encodedhmac...
Always verify the signature before processing the payload.
- TypeScript
- Python
import { verifyWebhook } from '@xavigate/node/webhooks';
// In your webhook handler (e.g., Express, Hono, Next.js API route):
app.post('/webhooks/xavigate', async (req, res) => {
const payload = await req.text();
const headers = {
'webhook-id': req.headers['webhook-id'],
'webhook-timestamp': req.headers['webhook-timestamp'],
'webhook-signature': req.headers['webhook-signature'],
};
try {
const event = verifyWebhook(payload, headers, process.env.XAVIGATE_WEBHOOK_SECRET);
// event.type, event.data, etc.
res.status(200).send('ok');
} catch (err) {
res.status(400).send('Invalid signature');
}
});
from xavigate.webhooks import verify_webhook
@app.route('/webhooks/xavigate', methods=['POST'])
def handle_webhook():
payload = request.get_data(as_text=True)
headers = {
'webhook-id': request.headers['webhook-id'],
'webhook-timestamp': request.headers['webhook-timestamp'],
'webhook-signature': request.headers['webhook-signature'],
}
try:
event = verify_webhook(payload, headers, os.environ['XAVIGATE_WEBHOOK_SECRET'])
# event['type'], event['data'], etc.
return 'ok', 200
except Exception:
return 'Invalid signature', 400
Webhook endpoints are public URLs. Without signature verification, anyone can POST fake events to your server. Never skip this step.
Retry behavior
If your endpoint returns anything other than 2xx, delivery is retried with exponential backoff:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | ~30 seconds |
| 3 | ~5 minutes |
| 4 | ~30 minutes |
| 5 | ~2 hours |
After 5 failed attempts over 24 hours, delivery stops and the endpoint is marked unhealthy. Re-deliveries are available from the dashboard.
Endpoint health
The dashboard shows each endpoint's health status:
- Active — delivering normally
- Unhealthy — failure rate high over the last hour
- Disabled — disabled after 7 days of consistent failure; re-enable manually
Testing webhooks
In sandbox, use webhook.site as a temporary endpoint to inspect event payloads. Register the URL in your sandbox dashboard and trigger events.