Webhooks & Events
Ztrace Documentation
Set up webhooks to receive real-time notifications about analysis completion, batch progress, and account events.
Available Events
| Event | Description | Payload |
|---|---|---|
| analysis.started | Analysis job began processing | analysis_id, image_id |
| analysis.completed | Analysis finished successfully | Full AnalysisResult |
| analysis.failed | Analysis encountered an error | error details |
| batch.started | Batch job began processing | batch_id, total_count |
| batch.progress | Batch progress update | completed_count, total_count |
| batch.completed | All batch items finished | summary statistics |
| quota.warning | Approaching quota limit | usage_percent, remaining |
| quota.exceeded | Quota limit reached | reset_date |
Webhook Implementation
webhook-handler.ts
TypeScript
1// Configure webhooks for async processing2<span <span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>class</span>=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>const</span> webhookConfig = <span <span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>class</span>=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>><span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>await</span></span> client.<span class="text-cyan-300">webhooks</span>.<span class="text-blue-400">create</span>({3 url: <span class="text-emerald-<span class="text-orange-400">400</span>">'https://your-app.<span class="text-cyan-300">com</span>/api/webhooks/ztrace'</span>,4 events: [5 <span class="text-emerald-<span class="text-orange-400">400</span>">'analysis.<span class="text-cyan-300">started</span>'</span>,6 <span class="text-emerald-<span class="text-orange-400">400</span>">'analysis.<span class="text-cyan-300">completed</span>'</span>, 7 <span class="text-emerald-<span class="text-orange-400">400</span>">'analysis.<span class="text-cyan-300">failed</span>'</span>,8 <span class="text-emerald-<span class="text-orange-400">400</span>">'batch.<span class="text-cyan-300">completed</span>'</span>,9 <span class="text-emerald-<span class="text-orange-400">400</span>">'quota.<span class="text-cyan-300">warning</span>'</span>,10 <span class="text-emerald-<span class="text-orange-400">400</span>">'quota.<span class="text-cyan-300">exceeded</span>'</span>11 ],12 secret: process.<span class="text-cyan-300">env</span>.<span class="text-cyan-300">WEBHOOK_SECRET</span>,13 metadata: {14 environment: <span class="text-emerald-<span class="text-orange-400">400</span>">'production'</span>15 }16});17 18console.<span class="text-blue-400">log</span>(<span class="text-emerald-<span class="text-orange-400">400</span>">'<span class="text-yellow-300">Webhook</span> <span class="text-yellow-300">ID</span>:'</span>, webhookConfig.<span class="text-cyan-300">id</span>);19console.<span class="text-blue-400">log</span>(<span class="text-emerald-<span class="text-orange-400">400</span>">'<span class="text-yellow-300">Signing</span> <span class="text-yellow-300">Secret</span>:'</span>, webhookConfig.<span class="text-cyan-300">signing_secret</span>);20 21// Webhook handler implementation22<span <span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>class</span>=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>><span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>import</span></span> { createHmac } <span <span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>class</span>=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>><span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>from</span></span> <span class="text-emerald-<span class="text-orange-400">400</span>">'crypto'</span>;23 24<span <span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>class</span>=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>export</span> <span <span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>class</span>=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>><span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>async</span></span> <span <span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>class</span>=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>function</span> <span class="text-blue-400"><span class="text-yellow-300">POST</span></span>(req: <span class="text-yellow-300">Request</span>) {25 <span <span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>class</span>=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>const</span> body = <span <span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>class</span>=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>><span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>await</span></span> req.<span class="text-blue-400">text</span>();26 <span <span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>class</span>=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>const</span> signature = req.<span class="text-cyan-300">headers</span>.<span <span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>class</span>=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>get</span>(<span class="text-emerald-<span class="text-orange-400">400</span>">'<span class="text-yellow-300">X</span>-<span class="text-yellow-300">Ztrace</span>-<span class="text-yellow-300">Signature</span>'</span>);27 <span <span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>class</span>=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>const</span> timestamp = req.<span class="text-cyan-300">headers</span>.<span <span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>class</span>=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>get</span>(<span class="text-emerald-<span class="text-orange-400">400</span>">'<span class="text-yellow-300">X</span>-<span class="text-yellow-300">Ztrace</span>-<span class="text-yellow-300">Timestamp</span>'</span>);28 29 // Verify webhook signature30 <span <span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>class</span>=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>const</span> expectedSig = <span class="text-blue-400">createHmac</span>(<span class="text-emerald-<span class="text-orange-400">400</span>">'sha256'</span>, process.<span class="text-cyan-300">env</span>.<span class="text-cyan-300">WEBHOOK_SECRET</span>!)31 .<span class="text-blue-400">update</span>(<span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-emerald-<span class="text-orange-400">400</span>"</span>>`${timestamp}.${body}`</span>)32 .<span class="text-blue-400">digest</span>(<span class="text-emerald-<span class="text-orange-400">400</span>">'hex'</span>);33 34 <span <span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>class</span>=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>><span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>if</span></span> (signature !== <span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-emerald-<span class="text-orange-400">400</span>"</span>>`sha256=${expectedSig}`</span>) {35 <span <span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>class</span>=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>><span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>return</span></span> <span <span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>class</span>=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>new</span> <span class="text-blue-400"><span class="text-yellow-300">Response</span></span>(<span class="text-emerald-<span class="text-orange-400">400</span>">'<span class="text-yellow-300">Invalid</span> signature'</span>, { status: <span class="text-orange-400">401</span> });36 }37 38 <span <span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>class</span>=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>const</span> { event, data, timestamp: eventTime } = <span class="text-yellow-300">JSON</span>.<span class="text-blue-400">parse</span>(body);39 40 <span <span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>class</span>=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>switch</span>(event) {41 <span <span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>class</span>=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>case</span> <span class="text-emerald-<span class="text-orange-400">400</span>">'analysis.<span class="text-cyan-300">completed</span>'</span>:42 <span <span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>class</span>=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>><span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>await</span></span> <span class="text-blue-400">handleAnalysisComplete</span>(data);43 <span <span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>class</span>=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>><span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>break</span></span>;44 <span <span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>class</span>=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>case</span> <span class="text-emerald-<span class="text-orange-400">400</span>">'analysis.<span class="text-cyan-300">failed</span>'</span>:45 <span <span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>class</span>=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>><span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>await</span></span> <span class="text-blue-400">handleAnalysisError</span>(data);46 <span <span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>class</span>=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>><span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>break</span></span>;47 <span <span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>class</span>=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>case</span> <span class="text-emerald-<span class="text-orange-400">400</span>">'batch.<span class="text-cyan-300">completed</span>'</span>:48 <span <span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>class</span>=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>><span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>await</span></span> <span class="text-blue-400">handleBatchComplete</span>(data);49 <span <span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>class</span>=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>><span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>break</span></span>;50 <span <span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>class</span>=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>case</span> <span class="text-emerald-<span class="text-orange-400">400</span>">'quota.<span class="text-cyan-300">warning</span>'</span>:51 <span <span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>class</span>=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>><span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>await</span></span> <span class="text-blue-400">sendQuotaAlert</span>(data);52 <span <span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>class</span>=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>><span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>break</span></span>;53 }54 55 <span <span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>class</span>=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>><span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>return</span></span> <span <span class=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>class</span>=<span class="text-emerald-<span class="text-orange-400">400</span>">"text-purple-<span class="text-orange-400">400</span> font-medium"</span>>new</span> <span class="text-blue-400"><span class="text-yellow-300">Response</span></span>(<span class="text-emerald-<span class="text-orange-400">400</span>">'<span class="text-yellow-300">OK</span>'</span>, { status: <span class="text-orange-400">200</span> });56}Webhook Retries
Failed webhook deliveries are retried up to 5 times with exponential backoff. Webhooks that fail consistently for 24 hours are automatically disabled.
Last updated December 2025
Was this helpful?