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 thisimport os
from wisprs import WisprsClient
client = WisprsClient(api_key=os.environ["WISPRS_API_KEY"])
endpoint = client.webhooks.create_endpoint(
url="https://your-app.com/webhooks/wisprs"
)
print(endpoint.signing_secret) # shown once — save thiscurl -X POST https://wisprs.co/api/v1/webhooks/endpoints \
-H "Authorization: Bearer sk_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "url": "https://your-app.com/webhooks/wisprs" }'{
"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.

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');
}import hmac, hashlib, os
from flask import Flask, request, abort
app = Flask(__name__)
@app.route("/webhooks/wisprs", methods=["POST"])
def handle_webhook():
body = request.get_data()
sig = request.headers.get("x-wisprs-signature", "")
secret = os.environ["WISPRS_WEBHOOK_SECRET"].encode()
expected = hmac.new(secret, body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(sig, expected):
abort(403)
event = request.get_json()
# handle event...
return "OK"# Signature verification must be done server-side.
# Use the TypeScript or Python example to implement the handler.
# The signature is sent in the x-wisprs-signature header on every delivery.Event payload
{
"event": "transcription.completed",
"jobId": 42,
"transcriptionId": 99,
"status": "completed",
"timestamp": "2026-06-17T10:00:00.000Z"
}Retry behaviour
Wisprs retries failed deliveries 3 times with exponential backoff (~1s, ~5s, ~30s). A delivery is considered failed if your endpoint returns a non-2xx status or times out after 10 seconds.
Test a webhook
await client.webhooks.test({
url: 'https://your-app.com/webhooks/wisprs',
});client.webhooks.test(url="https://your-app.com/webhooks/wisprs")curl -X POST https://wisprs.co/api/v1/webhooks/test \
-H "Authorization: Bearer sk_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "url": "https://your-app.com/webhooks/wisprs" }'FAQ
Are failed webhook deliveries retried automatically?
Yes. If your endpoint returns a non-2xx status or times out after 10 seconds, Wisprs retries the delivery 3 times with exponential backoff (~1s, ~5s, ~30s). After all retries are exhausted the delivery is marked as failed.
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.