Webhook Events & Real-Time Job Notifications

Webhooks let your app react the moment a transcription job completes or fails — no polling required. Register an endpoint and Wisprs will POST a signed payload to your URL.

Why webhooks instead of polling?

Polling GET /api/v1/jobs/{id} works fine for short scripts, but for production systems webhooks are more reliable and efficient. Your server only wakes up when there's something to handle, and you get completion events even if your polling loop restarts or misses a cycle.

Register an endpoint

import { WisprsClient } from '@wisprs/sdk'; const client = new WisprsClient({ apiKey: process.env.WISPRS_API_KEY! }); const endpoint = await client.webhooks.createEndpoint({ url: 'https://your-app.com/webhooks/wisprs', }); console.log(endpoint.signingSecret); // shown once — save this
json
{ "success": true, "data": { "endpoint": { "id": 7, "url": "https://your-app.com/webhooks/wisprs", "signingSecret": "whsec_your_secret", "isActive": true } } }

Save the signingSecret — it's shown once and used to verify signatures.

Webhook endpoint registration form
Dashboard → Settings → API → Webhook Endpoints — register and test your callback URL

Verify the signature

import { createHmac, timingSafeEqual } from 'node:crypto'; export async function POST(req: Request) { const body = await req.text(); const sig = req.headers.get('x-wisprs-signature') ?? ''; const expected = createHmac('sha256', process.env.WISPRS_WEBHOOK_SECRET!) .update(body) .digest('hex'); if (!timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) { return new Response('Forbidden', { status: 403 }); } const event = JSON.parse(body); // handle event... return new Response('OK'); }

Event payload

json
{ "event": "transcription.completed", "data": { "transcriptionId": 99, "status": "completed", "fileName": "interview.mp3", "language": "en", "duration": 123, "completedAt": "2026-06-17T10:00:00.000Z" } }

Retry behaviour

Terminal events are stored before delivery and survive app restarts. Wisprs makes an initial attempt, then up to five retries (after 1 minute, 5 minutes, 30 minutes, 2 hours, then 12 hours); each attempt may make up to three short HTTP attempts. Return a 2xx response within 10 seconds. Delivery is at least once, so deduplicate on event type, transcription ID, and retry count.

Test a webhook

await client.webhooks.test({ url: 'https://your-app.com/webhooks/wisprs', });

FAQ

Are failed webhook deliveries retried automatically?

Yes. Wisprs keeps the event in a durable outbox and retries up to five times after the initial attempt. Poll GET /api/v1/jobs/{id} for pending, delivered, and failed webhook counts. The endpoint delivery log records individual HTTP attempts.

How do I verify that a webhook came from Wisprs?

Every delivery includes an x-wisprs-signature header. Compute an HMAC-SHA256 of the raw request body using your signing secret and compare it using a constant-time comparison. The example above shows the full verification pattern.

Can I receive webhooks for specific events only?

Currently all endpoints receive transcription.completed and transcription.failed events. Per-event filtering is on the roadmap.