Skip to main content

Webhook Overview

Scriptonia receives Dodo webhooks at POST /api/dodo/webhook to update workflow state when a payment succeeds, fails, or is refunded. Other webhooks (e.g. GitHub) are outside this doc.

Dodo Webhook

Endpoint

POST /api/dodo/webhook
Content-Type: application/json
  • Body parsing: Disabled (api.bodyParser: false) so the raw body can be used for signature verification.

Event Types

Handled in pages/api/dodo/webhook.ts:
EventAliasAction
payment.succeededpayment_succeeded, payment.completedRead data or root; metadata.session_id, metadata.platform; data.payment_id or data.id. Update Workflow: paymentMethod: 'dodo', dodoPaymentId, currentStage: 'payment_processing', selectedPlatform (from metadata).
payment.failedpayment_failedLog; optionally update workflow.
refund.succeededrefund_succeededIf metadata.session_id, set paymentMethod: null, dodoPaymentId: null on Workflow.
(other)Log as unhandled; still return 200.

Security

Signature verification

  • Header: x-dodo-signature or dodo-signature (single string; if array, treated as invalid).
  • Secret: DODO_WEBHOOK_SECRET. If unset, verification is skipped (insecure in production).
  • Algorithm: HMAC-SHA256 of the raw body (UTF-8), hex-encoded; compared with crypto.timingSafeEqual to the header value.
  • On invalid signature: 401 { received: false, error: "Invalid signature" }.

Raw body

  • The handler reads the raw body from req (stream) because Next.js does not parse it when bodyParser: false. The same bytes must be used for HMAC and for JSON.parse.

Retry Logic

  • Server behavior: The handler always returns 200 (or 401 on bad signature) so Dodo does not retry on “success.”
  • On processing errors (e.g. DB update fails): the handler still returns 200 and { received: true, error: message } to avoid retries for malformed or unprocessable payloads. Log errors for debugging.
  • Dodo: Configure retries and backoff in the Dodo dashboard. No custom retry logic is implemented in Scriptonia.

Examples

payment.succeeded (conceptual)

{
  "type": "payment.succeeded",
  "data": {
    "payment_id": "pay_xxx",
    "id": "pay_xxx",
    "metadata": {
      "session_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "platform": "Web Application"
    }
  }
}
Handler: prisma.workflow.update({ where: { sessionId }, data: { paymentMethod: 'dodo', dodoPaymentId, currentStage: 'payment_processing', selectedPlatform } }).

Refund

{
  "type": "refund.succeeded",
  "data": {
    "metadata": { "session_id": "a1b2c3d4-..." }
  }
}
Handler: prisma.workflow.update({ where: { sessionId }, data: { paymentMethod: null, dodoPaymentId: null } }).