Webhooks
Receive, verify, and handle Nexa webhook events for file processing, payments, and student email provisioning.
How webhooks work
When a file finishes processing or a payment is confirmed, Nexa sends a signed POST request to a URL you register in the dashboard. The SDK:
- Verifies the HMAC-SHA256 signature using your
NEXA_WEBHOOK_SECRET(your API key — see setup below) - Rejects events older than 5 minutes (replay protection)
- Parses the payload into a typed
WebhookEvent - Calls your
onFileCompleteoronPaymentCompletecallback
This happens automatically inside the catch-all route handler for those two events only. Student email events (email.created, email.status_changed, email.bulk_completed) are not auto-dispatched by createNexa() — there's no matching callback field, and the catch-all route has no built-in path for them. See Email events below.
Setup checklist
1. Register your webhook URLs in the Nexa dashboard:
| Event | URL |
|---|---|
file.completed | https://your-domain.com/api/nexa/webhook |
payment.completed | https://your-domain.com/api/nexa/payments/forward |
email.* (created / status_changed / bulk_completed) | A route you write yourself, e.g. https://your-domain.com/api/nexa/email-webhook — see below |
2. Confirm NEXA_WEBHOOK_SECRET is set in your environment:
Info
Nexa signs every webhook with HMAC-SHA256 using your API key as the signing
secret. There is no separate webhook secret to generate today — set
NEXA_WEBHOOK_SECRET to the same value as NEXA_API_KEY, and update both
whenever you rotate the key.
3. Implement your callbacks in lib/nexa.ts:
createNexa() only accepts onFileComplete and onPaymentComplete — there is no onEmailBulkCompleted (or any other email-event) callback.
Manual verification
If you need to verify a webhook in a custom route outside the SDK — required for any email event, optional for file/payment events if you want to handle them yourself instead of using the built-in callbacks:
You can also adjust the replay protection window:
Email events need a custom route
There's no /api/nexa/email-* path in the catch-all route handler and no email-event callback on createNexa(). Registering a URL like /api/nexa/email-provisioning/forward in the dashboard will 404 — write your own route and verify manually:
Register https://your-domain.com/api/nexa/email-webhook in the dashboard for the email events you use.
WebhookEvent types
Discriminated on event (not type):
WebhookFileCompleteEvent
The event itself carries no result payload or status — it's a signal to go fetch the latest state via nexa.documents.getFileDetails() / nexa.documents.getStudentRecords(), or nexa.documents.getLatestJob() if you need to know whether extraction succeeded or failed.
WebhookPaymentEvent
WebhookEmailCreatedEvent
Fired after a single student email account is successfully provisioned.
WebhookEmailStatusChangedEvent
Fired when an account is suspended or restored.
WebhookEmailBulkCompletedEvent
Fired once when a bulk provisioning job reaches completed or failed. The payload is lean — counts only. Call nexa.email.getJobStatus(jobId) to retrieve the full per-student breakdown.
Info
Why no per-student data in the webhook?
Pushing individual account details for each student in a 500-student job would make the webhook body extremely large and expensive to receive and process. Instead, the webhook tells you the job is done — you pull the full results only when you need them via nexa.email.getJobStatus(jobId).
Convex integration
@nexa-ed/convex provides pre-built handlers that sync file.completed and payment.completed events straight into your Convex backend, wired the normal way through createNexa():
This writes to the nexaFileResults and paymentTransactions tables defined by @nexa-ed/convex/schema.
For student email events, @nexa-ed/convex also exports createStudentEmailHandler(convex, api) — but since there's no createNexa() callback for email events (see above), call it yourself from the custom route:
Testing webhooks locally
Use ngrok or Cloudflare Tunnel to expose your local server:
Register https://abc123.ngrok.io/api/nexa/webhook in the Nexa dashboard temporarily, then trigger a test event from the dashboard to verify your handler is working.
What's next
- File Processing — submit files and trigger
file.completedevents - Payments — initialize payments and trigger
payment.completedevents - Student Emails — provision accounts and receive
email.created/email.bulk_completedevents - Reference — Types — full type definitions