Or jump from this list on a smaller screen.

This is a page from the Audivra documentation. Developer exportsindex: 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.

EndpointProviderPrimary events
/api/webhooks/stripeStripecheckout.session.completed · invoice.paid · invoice.payment_failed · customer.subscription.updated · customer.subscription.deleted · payment_intent.succeeded
/api/webhooks/paystackPaystackcharge.success
/api/webhooks/flutterwaveFlutterwavecharge.completed
/api/webhooks/dlocaldLocalpayment.approved
POST
/api/webhooks/stripe
Stripe

Environment variables

Set in .env — see .env.example

VariableRole
STRIPE_SECRET_KEYStripe API secret — required to construct events.
STRIPE_WEBHOOK_SECRETSigning 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.

HeaderRequiredDescription
stripe-signatureYesHMAC-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.

Example payload
{
  "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.

Example payload
{
  "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.

Example payload
{
  "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.

Example payload
{
  "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.

Example payload
{
  "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.

Example payload
{
  "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)

JSON
{ "received": true }

Error responses

StatusMeaning
400Missing stripe-signature or invalid payload/signature
503STRIPE_SECRET_KEY or STRIPE_WEBHOOK_SECRET not configured
POST
/api/webhooks/paystack
Paystack

Environment variables

Set in .env — see .env.example

VariableRole
PAYSTACK_SECRET_KEYPaystack secret key for API calls.
PAYSTACK_WEBHOOK_SECRETShared 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.

HeaderRequiredDescription
x-paystack-signatureIf configuredPrimary header — compared to PAYSTACK_WEBHOOK_SECRET when set.
authorizationIf configuredFallback 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).

Example payload
{
  "event": "charge.success",
  "data": {
    "reference": "T123456789",
    "amount": 290000,
    "currency": "KES",
    "customer": {
      "email": "user@example.com"
    },
    "plan": "creator"
  }
}

Success response (200)

JSON
{ "received": true, "reference": "T123456789" }

Error responses

StatusMeaning
401Invalid x-paystack-signature or shared-secret header
400Missing customer email in payload
503PAYSTACK_SECRET_KEY and PAYSTACK_WEBHOOK_SECRET not configured
POST
/api/webhooks/flutterwave
Flutterwave

Environment variables

Set in .env — see .env.example

VariableRole
FLUTTERWAVE_SECRET_KEYFlutterwave secret key for API calls.
FLUTTERWAVE_WEBHOOK_SECRETSecret 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.

HeaderRequiredDescription
verif-hashYesMust 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).

Example payload
{
  "event": "charge.completed",
  "data": {
    "tx_ref": "audivra-creator-abc123",
    "amount": 29,
    "currency": "USD",
    "customer": {
      "email": "user@example.com"
    },
    "plan": "creator"
  }
}

Success response (200)

JSON
{ "received": true, "reference": "audivra-creator-abc123" }

Error responses

StatusMeaning
401Invalid verif-hash header
400Missing data.customer.email
503FLUTTERWAVE_WEBHOOK_SECRET not configured
POST
/api/webhooks/dlocal
dLocal

Environment variables

Set in .env — see .env.example

VariableRole
DLOCAL_API_KEYdLocal API key.
DLOCAL_SECRET_KEYdLocal secret for signing API requests.
DLOCAL_WEBHOOK_SECRETShared secret for inbound webhook verification.

Signature headers

When DLOCAL_WEBHOOK_SECRET is set, header must match exactly. Returns 401 on mismatch.

HeaderRequiredDescription
x-dlocal-signatureIf configuredPrimary signature header — compared to DLOCAL_WEBHOOK_SECRET.
authorizationIf configuredFallback 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).

Example payload
{
  "id": "PAY-123456789",
  "status": "PAID",
  "amount": 29.0,
  "currency": "BRL",
  "payer": {
    "email": "user@example.com"
  },
  "plan": "starter"
}

Success response (200)

JSON
{ "received": true, "reference": "PAY-123456789" }

Error responses

StatusMeaning
401Invalid x-dlocal-signature header
400Missing payer.email
503DLOCAL_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)

Request body
{ "webhookUrl": "https://your-app.com/webhooks/audivra" }

POST /api/v1/avatar

Bearer sk_live_…

Request body
{ "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.

HeaderValue
Content-Typeapplication/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.

Payload Audivra POSTs to your URL
{
  "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.

Payload Audivra POSTs to your URL
{
  "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.

Payload Audivra POSTs to your URL
{
  "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.

Payload Audivra POSTs to your URL
{
  "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.

Payload Audivra POSTs to your URL
{
  "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.

Payload Audivra POSTs to your URL
{
  "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.

Payload Audivra POSTs to your URL
{
  "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