Skip to main content

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

  1. In your dashboard, go to WebhooksAdd endpoint
  2. Enter your URL (must be HTTPS in production; HTTP allowed in sandbox)
  3. Select which events to receive
  4. Copy the per-endpoint signing secret

You can register multiple endpoints. Each has its own signing secret.


Events

EventWhen
nature.assessment.completedScoring finished; profile is ready
nature.profile.updatedA 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.

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
Always verify signatures

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:

AttemptDelay
1Immediate
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.