Webhooks
Receive real-time notifications when conversions complete or fail. Webhooks eliminate the need for polling and enable event-driven workflows.
Overview
LogTalk supports two types of webhooks:
The same delivery mechanism is used for both changelog conversions (LogTalk) and document conversions (DocTalk) — the event type in the payload (conversion.* vs doctalk.*) tells you which product produced the event.
webhook_url in your conversion request. The callback is sent only for that specific conversion. Ideal for CI/CD pipelines.Per-Request Webhooks
Include a webhook_url in your conversion request to receive a callback when that specific conversion completes:
curl -X POST https://logtalk.io/api/v1/conversions \-H "Authorization: Bearer lt_live_your_key" \-H "Content-Type: application/json" \-d '{"source_type": "changelog","content": "## v2.0.0\n- New feature","webhook_url": "https://your-app.com/webhooks/logtalk"}'
Note: Webhook URLs must use HTTPS. HTTP URLs will be rejected.
The same webhook_url parameter works for DocTalk conversions — pass "source_type": "document" instead of "changelog", and you'll receive doctalk.completed / doctalk.failed events with the DocTalk payload shape shown below.
Event Types
| Event | Description |
|---|---|
conversion.completed | LogTalk changelog conversion finished successfully with audio (or video) ready |
conversion.failed | LogTalk changelog conversion failed with error details |
doctalk.completed | DocTalk document conversion finished successfully with audio ready |
doctalk.failed | DocTalk document conversion failed with error details |
quota.warning | Approaching quota limit (80%) |
quota.exceeded | Monthly quota exceeded |
Webhook Payload
Webhooks are sent as POST requests with a JSON body. Every payload shares the same envelope — id, type, created_at, api_version, and data — but the shape of data depends on which product produced the conversion.
LogTalk (changelog conversions)
conversion.completed and conversion.failed are sent for changelog conversions created with "source_type": "changelog".
conversion.completed
{"id": "evt_abc123def456","type": "conversion.completed","created_at": "2026-01-18T14:32:15.000Z","api_version": "2026-01-18","data": {"id": "550e8400-e29b-41d4-a716-446655440000","status": "completed","mode": "changelog","product_name": "Acme Widget","version": "2.0.0","tone": "professional","duration": "short","output_format": "audio","source": "api","audio_url": "https://logtalk.io/api/v1/conversions/550e8400-e29b-41d4-a716-446655440000/audio","video_url": null,"audio_duration_seconds": 180,"script_text": "Here's what's new in Acme Widget 2.0.0...","listen_url": "https://logtalk.io/listen/550e8400-e29b-41d4-a716-446655440000","embed_url": "https://logtalk.io/embed/550e8400-e29b-41d4-a716-446655440000","created_at": "2026-01-18T14:30:00.000Z","completed_at": "2026-01-18T14:32:15.000Z"}}
listen_url and embed_url are null unless the conversion is public. video_url is null for audio-only conversions.
conversion.failed
{"id": "evt_abc123def456","type": "conversion.failed","created_at": "2026-01-18T14:31:00.000Z","api_version": "2026-01-18","data": {"id": "550e8400-e29b-41d4-a716-446655440000","status": "failed","mode": "changelog","product_name": "Acme Widget","version": "2.0.0","tone": "professional","duration": "short","output_format": "audio","source": "api","created_at": "2026-01-18T14:30:00.000Z","failed_at": "2026-01-18T14:31:00.000Z","error": {"code": "GENERATION_FAILED","message": "Audio generation failed"}}}
DocTalk (document conversions)
doctalk.completed and doctalk.failed are sent for document conversions created with "source_type": "document". The datashape is different from LogTalk's — it has no mode or version field, and instead carries source_type, verbosity, and speakers.
doctalk.completed
{"id": "evt_def789ghi012","type": "doctalk.completed","created_at": "2026-01-18T14:32:15.000Z","api_version": "2026-01-18","data": {"id": "7c9e6679-7425-40de-944b-e07fc1f90ae7","status": "completed","source_type": "document","content_description": "Q3 product roadmap.pdf","tone": "casual","verbosity": "normal","speakers": 2,"source": "api","audio_url": "https://doctalk.io/api/v1/conversions/7c9e6679-7425-40de-944b-e07fc1f90ae7/audio","audio_duration_seconds": 240,"script_text": "Speaker 1: Let's dig into the Q3 roadmap...","listen_url": "https://doctalk.io/listen/7c9e6679-7425-40de-944b-e07fc1f90ae7","embed_url": "https://doctalk.io/embed/7c9e6679-7425-40de-944b-e07fc1f90ae7","created_at": "2026-01-18T14:29:00.000Z","completed_at": "2026-01-18T14:32:15.000Z"}}
speakers is 1 or 2 depending on whether the conversion was generated as a monologue or two-speaker dialogue. source is always "api" — DocTalk conversions are API-only.
doctalk.failed
{"id": "evt_def789ghi012","type": "doctalk.failed","created_at": "2026-01-18T14:31:00.000Z","api_version": "2026-01-18","data": {"id": "7c9e6679-7425-40de-944b-e07fc1f90ae7","status": "failed","source_type": "document","content_description": "Q3 product roadmap.pdf","tone": "casual","verbosity": "normal","speakers": 2,"source": "api","audio_url": null,"audio_duration_seconds": null,"script_text": null,"listen_url": null,"embed_url": null,"created_at": "2026-01-18T14:29:00.000Z","failed_at": "2026-01-18T14:31:00.000Z","error": {"code": "GENERATION_FAILED","message": "Could not extract readable text from the uploaded document"}}}
Webhook Headers
Each webhook request includes these headers:
| Header | Description |
|---|---|
Content-Type | Always application/json |
X-LogTalk-Event | Event type (e.g., conversion.completed) |
X-LogTalk-Delivery | Unique delivery ID for debugging |
X-LogTalk-Signature | HMAC-SHA256 signature (format: t=timestamp,v1=signature) |
X-LogTalk-Timestamp | Unix timestamp when webhook was sent |
Signature Verification
Always verify webhook signatures to ensure requests are from LogTalk. Signatures use HMAC-SHA256 with the format t=timestamp,v1=signature.
${timestamp}.${JSON.stringify(payload)}This prevents replay attacks by binding the signature to the timestamp.
Verification Examples
const crypto = require('crypto');function verifyWebhookSignature(payload, signatureHeader, secret) {// Parse signature header: t=timestamp,v1=signatureconst parts = signatureHeader.split(',');const timestamp = parseInt(parts.find(p => p.startsWith('t=')).split('=')[1]);const signature = parts.find(p => p.startsWith('v1=')).split('=')[1];// Check timestamp (reject if > 5 minutes old)const now = Math.floor(Date.now() / 1000);if (Math.abs(now - timestamp) > 300) {throw new Error('Webhook timestamp too old');}// Compute expected signatureconst signedPayload = `${timestamp}.${JSON.stringify(payload)}`;const expectedSignature = crypto.createHmac('sha256', secret).update(signedPayload).digest('hex');// Use timing-safe comparisonreturn crypto.timingSafeEqual(Buffer.from(signature, 'hex'),Buffer.from(expectedSignature, 'hex'));}// Express.js exampleapp.post('/webhooks/logtalk', express.json(), (req, res) => {const signature = req.headers['x-logtalk-signature'];const secret = process.env.LOGTALK_WEBHOOK_SECRET;if (!verifyWebhookSignature(req.body, signature, secret)) {return res.status(401).send('Invalid signature');}// Process the webhookconst { type, data } = req.body;console.log(`Received ${type}: ${data.id}`);res.status(200).send('OK');});
Retry Policy
If your endpoint returns a non-2xx status code or times out, LogTalk will retry with exponential backoff:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
| 6 | 8 hours |
After 6 failed attempts, the webhook is marked as failing and you'll be notified via email. Webhook timeout is 30 seconds.
Best Practices
id to deduplicate events and ensure idempotent processing.X-LogTalk-Signatureheader to ensure webhooks are authentic and haven't been tampered with.