Webhooks
Webhooks notify your backend about events such as authorization decisions, completed challenges, issued or revoked proofs, enrollment completion, and subject revocation. Use them to update operational views and trigger reconciliation. Protected execution uses the synchronous authorization and proof-verification flow.
Use delivery retries, dead letters, and replay controls to manage event processing. Design your receiver for duplicate and out-of-order events, and periodically reconcile against authenticated API records to maintain a complete operational view.
Register a destination
Use the Console Integrations view to configure a public HTTPS endpoint in the intended environment, choose relevant event subscriptions, and obtain the signing secret. Keep it in your backend's secret store. Private, loopback, link-local, and unsafe destinations are rejected, including when a delivery resolves its destination again.
Save the secret when issued and prepare a rotation procedure. The endpoint should durably accept a valid event before returning success, then process it asynchronously. Keep the request handler fast and bound its body size.
Verify the exact raw body
For new integrations require all three V2 headers: X-GrayPass-Signature-V2, X-GrayPass-Timestamp, and X-GrayPass-Delivery-Id. V2 signs the timestamp, delivery ID, and exact raw body bytes. It checks a default freshness tolerance of 300 seconds. Maintain a synchronized server clock.
Add this Express handler to an existing app with a configured signing secret, expected tenant and environment IDs, and your own durable inbox.
import express from 'express';
import { parseWebhook } from '@graypass/node';
app.post('/webhooks/graypass',
express.raw({ type: 'application/json', limit: '256kb' }),
async (req, res) => {
const signature = req.get('X-GrayPass-Signature-V2');
const timestamp = req.get('X-GrayPass-Timestamp');
const deliveryId = req.get('X-GrayPass-Delivery-Id');
if (!signature || !timestamp || !deliveryId) {
return res.sendStatus(400);
}
const event = parseWebhook(
req.body,
req.get('X-GrayPass-Signature'),
process.env.GRAYPASS_WEBHOOK_SECRET!,
{ signature, timestamp, deliveryId },
);
const eventId = event?.metadata?.event_id;
if (!event || typeof eventId !== 'string' || !eventId) {
return res.sendStatus(400);
}
if (event.tenant_id !== tenantId ||
event.metadata?.environment !== environmentId) {
return res.sendStatus(400);
}
try {
await durableInbox.insertIfAbsent(eventId, event);
return res.sendStatus(200);
} catch {
return res.sendStatus(503);
}
});
Mount this route before JSON-parsing middleware. tenantId and environmentId are the intended values from your trusted configuration or credential identity. durableInbox represents your own durable, unique-key event inbox. Its insert and acceptance must survive a process crash. If storage fails, return a failure response so delivery can be retried; acknowledge only after the event is durable. Validate the expected event shape and apply your business permissions before downstream processing.
The three-argument parseWebhook form retains body-only signature compatibility. Explicitly rejecting missing V2 headers prevents accidental downgrade to that form. Passing a whole headers object as the second argument is incorrect.
Deduplication and processing
For canonical events, use metadata.event_id as the business processing deduplication key; retain delivery IDs separately for diagnostics. Repeated delivery or replay must not repeat a mutation. Mark processing completion durably, and design retry behavior for failures after an external side effect.
Do not infer ordering from arrival time. Fetch the current authorization or subject state through the authenticated API when acting on lifecycle-sensitive information. Reconciliation must not depend solely on having received every event.
Deleting a subject does not purge events already queued for delivery or held as dead letters. Retain an appropriate deletion marker in your receiver and ignore stale lifecycle events for a deleted subject; never recreate a subject solely because an older webhook arrives. Apply your retention policy to the inbox and deletion marker. See Privacy for deletion boundaries.
Rotation and recovery
During a rotation overlap, a signature header may contain multiple comma-separated signatures. The SDK accepts a matching signature for the configured secret. Update your secret through the approved rotation process, test the endpoint, and avoid logging either secret or full signature material.
Delivery failures that exhaust retry handling can appear as dead letters in Console. Inspect the failure, correct the endpoint or authentication issue, and use the replay or discard control intentionally. A failed destination resolution before an initial delivery may require sending another test event after the destination is fixed.
Monitor the age of your last received event and your own reconciliation lag. An absence of events may mean no activity, a transport failure, or an enqueue gap. For a consequential action, use the synchronous decision and proof flow described in Integration.