Or jump from this list on a smaller screen.
This is a page from the Audivra documentation. Developer exports — index: http://127.0.0.1:3000/docs/llms.txt, full corpus: http://127.0.0.1:3000/docs/llms-full.txt, OpenAPI: http://127.0.0.1:3000/docs/openapi.json, Postman: http://127.0.0.1:3000/docs/postman.json. Press ⌘K to search docs.
Webhooks
Payment provider webhooks upgrade plans and credit wallets. Job completion webhooks notify your server when async renders finish — with exact payload schemas, signature headers, retry behavior, and idempotency notes.
Overview
Inbound payment webhooks upgrade plans and prepaid credit wallets. Outbound job webhooks notify your server when avatar renders complete or fail. This page documents exact JSON shapes, signature headers, retry behavior, and idempotency as implemented in src/app/api/webhooks/*.
Idempotency & duplicate deliveries
Audivra persists inbound payment webhook keys in webhook_events (Supabase) or .data/store.json (dev). Duplicate Stripe event.id, Paystack reference, Flutterwave tx_ref, or dLocal payment id returns HTTP 200 with { "duplicate": true } without re-processing. Job webhooks fire at most once per terminal event (video.completed or video.failed) — tracked on video_jobs.webhook_notified_event. Clone webhooks (clone.ready / clone.failed) fire at most once per clone — tracked on voice_clones.webhook_notified_event.
Retry behavior
Inbound: Stripe, Paystack, Flutterwave, and dLocal retry failed deliveries (non-2xx or timeout) with exponential backoff for up to several days. Return 200 only after your handler succeeds. Audivra returns 400/401 for invalid signatures and 503 when the provider signing secret is missing — unsigned payment webhooks are never accepted.
Outbound: Celery workers POST job webhooks once — 30s timeout on success, 15s on failure. Failed delivery is logged; there is no automatic retry from Audivra. Use GET /api/v1/jobs/{id} or SSE /api/v1/jobs/{id}/events as a reliable fallback. Voice clone webhooks (clone.ready / clone.failed) follow the same delivery semantics.
Payment webhooks (inbound)
Register these URLs in each provider dashboard. See the M-Pesa via Paystack guide for an end-to-end walkthrough.
| Endpoint | Provider | Primary events |
|---|---|---|
| /api/webhooks/stripe | Stripe | checkout.session.completed · invoice.paid · invoice.payment_failed · customer.subscription.updated · customer.subscription.deleted · payment_intent.succeeded |
| /api/webhooks/paystack | Paystack | charge.success |
| /api/webhooks/flutterwave | Flutterwave | charge.completed |
| /api/webhooks/dlocal | dLocal | payment.approved |
/api/webhooks/stripeEnvironment variables
Set in .env — see .env.example
| Variable | Role |
|---|---|
| STRIPE_SECRET_KEY | Stripe API secret — required to construct events. |
| STRIPE_WEBHOOK_SECRET | Signing secret from Stripe Dashboard → Webhooks → endpoint. |
Signature headers
Raw request body must be passed verbatim to constructEvent — do not parse JSON before verification. Missing or invalid signatures return 400.
| Header | Required | Description |
|---|---|---|
| stripe-signature | Yes | HMAC-SHA256 timestamped signature. Verified with stripe.webhooks.constructEvent(). |
Provider retries
Stripe retries for up to 3 days with exponential backoff when Audivra returns non-2xx. Duplicate evt_… deliveries are possible — see idempotency notes.
Idempotency
Stripe event.id stored in webhook_events. Duplicates return { received: true, duplicate: true }.
checkout.session.completed
User finished Stripe Checkout for a subscription plan.
Audivra action: Sets user plan from metadata.plan (starter · creator · developer). Persists stripeCustomerId on the profile.
{
"id": "evt_…",
"type": "checkout.session.completed",
"data": {
"object": {
"id": "cs_…",
"customer": "cus_…",
"metadata": {
"audivra_user_id": "usr_…",
"plan": "creator"
}
}
}
}invoice.paid
Subscription invoice paid — monthly renewal.
Audivra action: Resets billing cycle counters for the Stripe customer. If profile was free or suspended, upgrades to creator.
{
"id": "evt_…",
"type": "invoice.paid",
"data": {
"object": {
"id": "in_…",
"customer": "cus_…",
"amount_paid": 2900
}
}
}invoice.payment_failed
Subscription renewal charge failed.
Audivra action: Sets plan to suspended — reduced character and video quotas until payment succeeds.
{
"id": "evt_…",
"type": "invoice.payment_failed",
"data": {
"object": {
"customer": "cus_…"
}
}
}customer.subscription.updated
Subscription status or metadata changed.
Audivra action: Maps status → plan: active uses metadata.plan; canceled/unpaid → free; past_due/incomplete → suspended.
{
"id": "evt_…",
"type": "customer.subscription.updated",
"data": {
"object": {
"customer": "cus_…",
"status": "active",
"metadata": { "plan": "developer" }
}
}
}customer.subscription.deleted
Subscription canceled or expired.
Audivra action: Downgrades matching profile to free tier.
{
"id": "evt_…",
"type": "customer.subscription.deleted",
"data": {
"object": { "customer": "cus_…" }
}
}payment_intent.succeeded
One-time prepaid credit top-up (PaymentIntent).
Audivra action: Adds amount_received (cents) to creditBalanceCents for the receipt email.
{
"id": "evt_…",
"type": "payment_intent.succeeded",
"data": {
"object": {
"amount_received": 5000,
"receipt_email": "user@example.com",
"metadata": { "audivra_user_email": "user@example.com" }
}
}
}Success response (200)
{ "received": true }Error responses
| Status | Meaning |
|---|---|
| 400 | Missing stripe-signature or invalid payload/signature |
| 503 | STRIPE_SECRET_KEY or STRIPE_WEBHOOK_SECRET not configured |
/api/webhooks/paystackEnvironment variables
Set in .env — see .env.example
| Variable | Role |
|---|---|
| PAYSTACK_SECRET_KEY | Paystack secret key for API calls. |
| PAYSTACK_WEBHOOK_SECRET | Shared secret compared against inbound signature header. |
Signature headers
HMAC-SHA512 of the raw request body using PAYSTACK_SECRET_KEY, compared to x-paystack-signature (timing-safe). Production accepts HMAC only. PAYSTACK_WEBHOOK_SECRET shared-secret comparison is local-dev fallback. Non charge.success events return 200 with ignored: true.
| Header | Required | Description |
|---|---|---|
| x-paystack-signature | If configured | Primary header — compared to PAYSTACK_WEBHOOK_SECRET when set. |
| authorization | If configured | Fallback header if x-paystack-signature is absent. |
Provider retries
Paystack retries failed webhook deliveries. Always return 200 after processing to stop retries.
Idempotency
Deduped by data.reference (or event+email fallback). Duplicates return { received: true, duplicate: true, reference }.
charge.success
Successful charge — M-Pesa, card, or bank transfer via Paystack.
Audivra action: Resolves customer email → profile. Applies data.plan when present. Adds data.amount / 100 to credit balance (Paystack amounts are in kobo).
{
"event": "charge.success",
"data": {
"reference": "T123456789",
"amount": 290000,
"currency": "KES",
"customer": {
"email": "user@example.com"
},
"plan": "creator"
}
}Success response (200)
{ "received": true, "reference": "T123456789" }Error responses
| Status | Meaning |
|---|---|
| 401 | Invalid x-paystack-signature or shared-secret header |
| 400 | Missing customer email in payload |
| 503 | PAYSTACK_SECRET_KEY and PAYSTACK_WEBHOOK_SECRET not configured |
/api/webhooks/flutterwaveEnvironment variables
Set in .env — see .env.example
| Variable | Role |
|---|---|
| FLUTTERWAVE_SECRET_KEY | Flutterwave secret key for API calls. |
| FLUTTERWAVE_WEBHOOK_SECRET | Secret hash configured in Flutterwave dashboard — sent as verif-hash. |
Signature headers
Compare verif-hash header to FLUTTERWAVE_WEBHOOK_SECRET. Returns 401 when configured and mismatched.
| Header | Required | Description |
|---|---|---|
| verif-hash | Yes | Must equal FLUTTERWAVE_WEBHOOK_SECRET when that env var is set. |
Provider retries
Flutterwave retries on non-2xx responses.
Idempotency
Deduped by data.tx_ref. Duplicates return { received: true, duplicate: true, reference }.
charge.completed
Successful Flutterwave charge (card, M-Pesa, MTN, etc.).
Audivra action: Maps data.customer.email → profile. Applies data.plan when present. Credits: data.amount × 100 (major currency units → cents).
{
"event": "charge.completed",
"data": {
"tx_ref": "audivra-creator-abc123",
"amount": 29,
"currency": "USD",
"customer": {
"email": "user@example.com"
},
"plan": "creator"
}
}Success response (200)
{ "received": true, "reference": "audivra-creator-abc123" }Error responses
| Status | Meaning |
|---|---|
| 401 | Invalid verif-hash header |
| 400 | Missing data.customer.email |
| 503 | FLUTTERWAVE_WEBHOOK_SECRET not configured |
/api/webhooks/dlocalEnvironment variables
Set in .env — see .env.example
| Variable | Role |
|---|---|
| DLOCAL_API_KEY | dLocal API key. |
| DLOCAL_SECRET_KEY | dLocal secret for signing API requests. |
| DLOCAL_WEBHOOK_SECRET | Shared secret for inbound webhook verification. |
Signature headers
When DLOCAL_WEBHOOK_SECRET is set, header must match exactly. Returns 401 on mismatch.
| Header | Required | Description |
|---|---|---|
| x-dlocal-signature | If configured | Primary signature header — compared to DLOCAL_WEBHOOK_SECRET. |
| authorization | If configured | Fallback when x-dlocal-signature is absent. |
Provider retries
dLocal retries failed notifications until a 2xx response is received.
Idempotency
Deduped by payment id. Duplicates return { received: true, duplicate: true, reference }.
payment.approved
Payment approved — Pix, UPI, Mobile Money, bank transfer, etc.
Audivra action: Maps payer.email → profile. Applies plan when present. Credits: amount × 100 (major units → cents).
{
"id": "PAY-123456789",
"status": "PAID",
"amount": 29.0,
"currency": "BRL",
"payer": {
"email": "user@example.com"
},
"plan": "starter"
}Success response (200)
{ "received": true, "reference": "PAY-123456789" }Error responses
| Status | Meaning |
|---|---|
| 401 | Invalid x-dlocal-signature header |
| 400 | Missing payer.email |
| 503 | DLOCAL_WEBHOOK_SECRET not configured |
Job completion webhooks (outbound)
Audivra POSTs to your URL when async avatar/video jobs reach a terminal state. Configure a default in the dashboard or override per request. Outbound URLs must be HTTPS in production; private, loopback, and cloud-metadata hosts are rejected (SSRF protection).
Configuration
PATCH /api/settings
Session cookie (dashboard)
{ "webhookUrl": "https://your-app.com/webhooks/audivra" }POST /api/v1/avatar
Bearer sk_live_…
{ "text": "…", "voice_id": "aurora", "avatar_id": "studio-host", "webhook_url": "https://…" }Set the default URL in /app/settings. Per-job override via webhook_url on POST /api/v1/avatar.
| Header | Value |
|---|---|
| Content-Type | application/json |
Audivra does not sign outbound job webhooks. Validate job_id via GET /api/v1/jobs/{id} if you need authenticity.
Celery workers POST job webhooks once — 30s timeout on success, 15s on failure. Failed delivery is logged; there is no automatic retry from Audivra. Use GET /api/v1/jobs/{id} or SSE /api/v1/jobs/{id}/events as a reliable fallback. Voice clone webhooks (clone.ready / clone.failed) follow the same delivery semantics.
video.completed
GPU pipeline finished — MP4 available.
Dispatched once when Celery worker marks job completed.
{
"event": "video.completed",
"job_id": "abc123def456",
"status": "completed",
"output_url": "https://cdn.example.com/renders/abc123.mp4",
"duration_seconds": 12
}video.failed
Pipeline failed after retries or quota error.
Dispatched once when job is marked failed. refunded_cents is prepaid credit returned to the wallet.
{
"event": "video.failed",
"job_id": "abc123def456",
"status": "failed",
"error": "Inference engine unavailable (502).",
"refunded_cents": 120
}batch.completed
All jobs in a generate batch reached completed or failed.
POST /api/v1/generate/batch webhook_url. Pass dry_run to validate JSONL without enqueueing.
{
"event": "batch.completed",
"id": "batch_abc",
"status": "COMPLETED",
"metrics": { "totalGenerations": 20, "successCount": 19, "failedCount": 1, "pendingCount": 0 }
}Voice clone webhooks (outbound)
Audivra POSTs to your URL when a voice clone reaches ready or failed. Pass webhook_url on POST /api/voices/clone or rely on the dashboard default from PATCH /api/settings.
- POST /api/voices/clone — Bearer sk_live_… or dashboard session
- PATCH /api/settings — Session cookie (dashboard) (Used as default when webhook_url is omitted on clone upload.)
Audivra does not sign outbound clone webhooks. Validate clone_id via GET /api/voices/clone if you need authenticity.
Celery workers POST job webhooks once — 30s timeout on success, 15s on failure. Failed delivery is logged; there is no automatic retry from Audivra. Use GET /api/v1/jobs/{id} or SSE /api/v1/jobs/{id}/events as a reliable fallback. Voice clone webhooks (clone.ready / clone.failed) follow the same delivery semantics.
clone.ready
GPU speaker prep finished — pass voice_id in TTS and generate.
Dispatched once when clone status becomes ready.
{
"event": "clone.ready",
"clone_id": "3f2c9a1e-…",
"voice_id": "3f2c9a1e-…",
"status": "ready",
"name": "Founder voice",
"engine": "f5-tts"
}clone.failed
Training failed — slot remains consumed until you DELETE the clone.
Dispatched once when clone status becomes failed.
{
"event": "clone.failed",
"clone_id": "3f2c9a1e-…",
"voice_id": "3f2c9a1e-…",
"status": "failed",
"error": "Sample is too short — upload 5–10 seconds of clean speech."
}Voice design webhooks (outbound)
Audivra POSTs to your URL when a voice design reaches ready or failed. Pass webhook_url on POST /api/voices/design or rely on the dashboard default from PATCH /api/settings.
- POST /api/voices/design — Bearer sk_live_… or dashboard session
Audivra does not sign outbound design webhooks. Validate design_id via GET /api/voices/design if you need authenticity.
Celery workers POST job webhooks once — 30s timeout on success, 15s on failure. Failed delivery is logged; there is no automatic retry from Audivra. Use GET /api/v1/jobs/{id} or SSE /api/v1/jobs/{id}/events as a reliable fallback. Voice clone webhooks (clone.ready / clone.failed) follow the same delivery semantics.
design.ready
Speaker reference generated — pass voice_id in TTS and generate.
Dispatched once when design status becomes ready.
{
"event": "design.ready",
"design_id": "8a1b2c3d-…",
"voice_id": "8a1b2c3d-…",
"status": "ready",
"name": "Product narrator",
"engine": "f5-tts",
"prompt": "Warm Kenyan English female narrator…"
}design.failed
Generation failed — slot remains consumed until you DELETE the design.
Dispatched once when design status becomes failed.
{
"event": "design.failed",
"design_id": "8a1b2c3d-…",
"voice_id": "8a1b2c3d-…",
"status": "failed",
"error": "Voice design produced no speaker reference"
}Step-by-step guide: Accept M-Pesa via Paystack webhook