Webhooks & Events
v1.0Subscribe to real-time events, configure webhook endpoints, and handle verification and consent notifications securely with HMAC verification.
Webhooks allow your application to receive asynchronous, real-time push notifications whenever an important event occurs in SyncNexa, such as a student completing verification, granting or revoking consent, or an application status update.
Why Webhooks?
Instead of polling the SyncNexa API repeatedly to check if a student has completed their verification session, webhooks notify your server the instant the cryptographic zero-knowledge proof is confirmed.
Supported Event Types
| Event Name | Description | Trigger Condition |
|---|---|---|
verification.completed | Verification successfully confirmed | Student approved verification and zk-proof validated |
verification.failed | Verification rejected or timed out | Proof validation failed or session expired without approval |
consent.approved | Student approved data sharing | Student granted application permission to access claims |
consent.denied | Student declined data sharing | Student explicitly rejected application permission request |
student.revoked | Student credential revoked | Issuing university marked the student as graduated or withdrawn |
app.updated | Application configuration updated | App metadata, redirect URIs, or credentials changed |
Registering a Webhook Endpoint
You can configure webhooks either during Application Creation (Step 2) or via the Webhooks dashboard in the Business Portal:
- Navigate to Webhooks in the Business Portal sidebar.
- Click Add Endpoint.
- Enter your HTTPS server endpoint URL (e.g.,
https://api.yourdomain.com/webhooks/syncnexa). - Select the events you want to subscribe to using the interactive event checkboxes.
- Save the endpoint. A unique Signing Secret (
whsec_...) will be generated for your endpoint.
Payload Structure
Every webhook notification is delivered as a POST request with a JSON payload:
| 1 | { |
| 2 | "id": "evt_1a2b3c4d5e6f7g8h", |
| 3 | "event": "verification.completed", |
| 4 | "createdAt": "2026-08-20T12:00:00.000Z", |
| 5 | "appId": "app_8a7b6c5d4e3f", |
| 6 | "environment": "live", |
| 7 | "data": { |
| 8 | "sessionId": "sess_9f8e7d6c5b4a", |
| 9 | "status": "completed", |
| 10 | "verified": true, |
| 11 | "university": { |
| 12 | "name": "Imperial College London", |
| 13 | "domain": "imperial.ac.uk", |
| 14 | "country": "GB" |
| 15 | }, |
| 16 | "proof": { |
| 17 | "type": "Groth16", |
| 18 | "proofHash": "0x4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b", |
| 19 | "expiresAt": "2027-06-30T23:59:59Z" |
| 20 | } |
| 21 | } |
| 22 | } |
Signature Verification (HMAC-SHA256)
SyncNexa signs every webhook delivery using your endpoint's signing secret (whsec_...). The signature is included in the X-SyncNexa-Signature HTTP header in the format t=timestamp,v1=signature_hex:
| 1 | import crypto from 'crypto'; |
| 2 | import express from 'express'; |
| 3 | |
| 4 | const app = express(); |
| 5 | |
| 6 | app.post( |
| 7 | '/webhooks/syncnexa', |
| 8 | express.raw({ type: 'application/json' }), |
| 9 | (req, res) => { |
| 10 | const signatureHeader = req.headers['x-syncnexa-signature'] as string; |
| 11 | const signingSecret = process.env.SYNCNEXA_WEBHOOK_SECRET!; // whsec_... |
| 12 | |
| 13 | if (!signatureHeader) { |
| 14 | return res.status(400).send('Missing signature header'); |
| 15 | } |
| 16 | |
| 17 | // 1. Extract timestamp and signature |
| 18 | const parts = signatureHeader.split(','); |
| 19 | const timestamp = parts.find((p) => p.startsWith('t='))?.split('=')[1]; |
| 20 | const expectedSig = parts.find((p) => p.startsWith('v1='))?.split('=')[1]; |
| 21 | |
| 22 | // 2. Compute expected HMAC |
| 23 | const signedPayload = `${timestamp}.${req.body.toString('utf8')}`; |
| 24 | const computedSig = crypto |
| 25 | .createHmac('sha256', signingSecret) |
| 26 | .update(signedPayload) |
| 27 | .digest('hex'); |
| 28 | |
| 29 | // 3. Constant-time comparison |
| 30 | const isValid = crypto.timingSafeEqual( |
| 31 | Buffer.from(computedSig), |
| 32 | Buffer.from(expectedSig || '') |
| 33 | ); |
| 34 | |
| 35 | if (!isValid) { |
| 36 | return res.status(401).send('Invalid webhook signature'); |
| 37 | } |
| 38 | |
| 39 | const payload = JSON.parse(req.body.toString('utf8')); |
| 40 | console.log('Verified Webhook Event:', payload.event); |
| 41 | |
| 42 | // Return 200 OK immediately |
| 43 | res.status(200).json({ received: true }); |
| 44 | } |
| 45 | ); |
Delivery & Retry Policy
SyncNexa expects your server to respond with an HTTP 2xx status code within 5 seconds. If your server returns an error code or times out, the webhook dispatcher will automatically retry delivery using exponential backoff:
- Immediate Attempt: At time 0
- Retry 1: 30 seconds later
- Retry 2: 5 minutes later
- Retry 3: 30 minutes later
- Retry 4: 2 hours later
- Final Retry: 24 hours later