webhook-integration
Complete guide for setting up and handling Dodo Payments webhooks for real-time payment event notifications.
What this skill does
# Dodo Payments Webhook Integration
**Reference: [docs.dodopayments.com/developer-resources/webhooks](https://docs.dodopayments.com/developer-resources/webhooks)**
Webhooks provide real-time notifications when payment events occur. Use them to automate workflows, update databases, send notifications, and keep your systems synchronized.
---
## Quick Setup
### 1. Configure Webhook in Dashboard
1. Go to Dashboard → Developer → Webhooks
2. Click "Create Webhook"
3. Enter your endpoint URL
4. Select events to subscribe to
5. Copy the webhook secret
### 2. Environment Variables
```bash
DODO_PAYMENTS_WEBHOOK_SECRET=your_webhook_secret_here
```
---
## Webhook Events
### Payment Events
| Event | Description |
|-------|-------------|
| `payment.succeeded` | Payment completed successfully |
| `payment.failed` | Payment attempt failed |
| `payment.processing` | Payment is being processed |
| `payment.cancelled` | Payment was cancelled |
### Subscription Events
| Event | Description |
|-------|-------------|
| `subscription.active` | Subscription is now active |
| `subscription.updated` | Subscription details changed |
| `subscription.on_hold` | Subscription on hold (failed renewal) |
| `subscription.renewed` | Subscription renewed successfully |
| `subscription.plan_changed` | Plan upgraded/downgraded |
| `subscription.cancelled` | Subscription cancelled |
| `subscription.failed` | Subscription creation failed |
| `subscription.expired` | Subscription term ended |
### Other Events
| Event | Description |
|-------|-------------|
| `refund.succeeded` | Refund processed successfully |
| `dispute.opened` | New dispute received |
| `license_key.created` | License key generated |
### Credit Events
| Event | Description |
|-------|-------------|
| `credit.added` | Credits granted to a customer (subscription, one-time, or API) |
| `credit.deducted` | Credits consumed through usage or manual debit |
| `credit.expired` | Unused credits expired after configured period |
| `credit.rolled_over` | Unused credits carried forward at cycle end |
| `credit.rollover_forfeited` | Credits forfeited at max rollover count |
| `credit.overage_charged` | Overage charges applied beyond zero balance |
| `credit.manual_adjustment` | Manual credit/debit adjustment via dashboard or API |
| `credit.balance_low` | Credit balance dropped below configured threshold |
---
## Webhook Payload Structure
### Request Headers
```http
POST /your-webhook-url
Content-Type: application/json
webhook-id: evt_xxxxx
webhook-signature: v1,signature_here
webhook-timestamp: 1234567890
```
### Payload Format
```json
{
"business_id": "bus_xxxxx",
"type": "payment.succeeded",
"timestamp": "2024-01-01T12:00:00Z",
"data": {
"payload_type": "Payment",
"payment_id": "pay_xxxxx",
"total_amount": 2999,
"currency": "USD",
"customer": {
"customer_id": "cust_xxxxx",
"email": "[email protected]",
"name": "John Doe"
}
// ... additional event-specific fields
}
}
```
---
## Implementation Examples
### Next.js (App Router)
```typescript
// app/api/webhooks/dodo/route.ts
import { NextRequest, NextResponse } from 'next/server';
import crypto from 'crypto';
const WEBHOOK_SECRET = process.env.DODO_PAYMENTS_WEBHOOK_SECRET!;
function verifySignature(payload: string, signature: string, timestamp: string): boolean {
const signedPayload = `${timestamp}.${payload}`;
const expectedSignature = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(signedPayload)
.digest('base64');
// Extract signature from "v1,signature" format
const providedSig = signature.split(',')[1];
return crypto.timingSafeEqual(
Buffer.from(expectedSignature),
Buffer.from(providedSig || '')
);
}
export async function POST(req: NextRequest) {
const body = await req.text();
const signature = req.headers.get('webhook-signature') || '';
const timestamp = req.headers.get('webhook-timestamp') || '';
const webhookId = req.headers.get('webhook-id');
// Verify signature
if (!verifySignature(body, signature, timestamp)) {
return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
}
// Check timestamp to prevent replay attacks (5 minute tolerance)
const eventTime = parseInt(timestamp) * 1000;
if (Math.abs(Date.now() - eventTime) > 300000) {
return NextResponse.json({ error: 'Timestamp too old' }, { status: 401 });
}
const event = JSON.parse(body);
// Handle events
switch (event.type) {
case 'payment.succeeded':
await handlePaymentSucceeded(event.data);
break;
case 'payment.failed':
await handlePaymentFailed(event.data);
break;
case 'subscription.active':
await handleSubscriptionActive(event.data);
break;
case 'subscription.cancelled':
await handleSubscriptionCancelled(event.data);
break;
case 'refund.succeeded':
await handleRefundSucceeded(event.data);
break;
case 'dispute.opened':
await handleDisputeOpened(event.data);
break;
case 'license_key.created':
await handleLicenseKeyCreated(event.data);
break;
case 'credit.added':
await handleCreditAdded(event.data);
break;
case 'credit.deducted':
await handleCreditDeducted(event.data);
break;
case 'credit.balance_low':
await handleCreditBalanceLow(event.data);
break;
default:
console.log(`Unhandled event type: ${event.type}`);
}
return NextResponse.json({ received: true });
}
async function handlePaymentSucceeded(data: any) {
const { payment_id, customer, total_amount, product_id, subscription_id } = data;
// Update database
// Send confirmation email
// Grant access to product
console.log(`Payment ${payment_id} succeeded for ${customer.email}`);
}
async function handlePaymentFailed(data: any) {
const { payment_id, customer, error_message } = data;
// Log failure
// Notify customer
// Update UI state
console.log(`Payment ${payment_id} failed: ${error_message}`);
}
async function handleSubscriptionActive(data: any) {
const { subscription_id, customer, product_id, next_billing_date } = data;
// Grant subscription access
// Update user record
// Send welcome email
console.log(`Subscription ${subscription_id} activated for ${customer.email}`);
}
async function handleSubscriptionCancelled(data: any) {
const { subscription_id, customer, cancelled_at, cancel_at_next_billing_date } = data;
// Schedule access revocation
// Send cancellation confirmation
console.log(`Subscription ${subscription_id} cancelled`);
}
async function handleRefundSucceeded(data: any) {
const { refund_id, payment_id, amount } = data;
// Update order status
// Revoke access if needed
console.log(`Refund ${refund_id} processed for payment ${payment_id}`);
}
async function handleDisputeOpened(data: any) {
const { dispute_id, payment_id, amount, dispute_status } = data;
// Alert team
// Prepare evidence
console.log(`Dispute ${dispute_id} opened for payment ${payment_id}`);
}
async function handleLicenseKeyCreated(data: any) {
const { id, key, product_id, customer_id, expires_at } = data;
// Store license key
// Send to customer
console.log(`License key created: ${key.substring(0, 8)}...`);
}
async function handleCreditAdded(data: any) {
const { customer_id, credit_entitlement_id, amount, balance_after } = data;
// Update internal credit balance
// Log credit grant
console.log(`${amount} credits added for customer ${customer_id}, balance: ${balance_after}`);
}
async function handleCreditDeducted(data: any) {
const { customer_id, credit_entitlement_id, amount, balance_after } = data;
// Update internal credit balance
// Check if balance is getting low
console.log(`${amount} credits deducted for customer ${customer_id}, balance: ${balance_after}`);
}
async function handleCreditBalanceLow(data: any) {
const { customerRelated in Sales & CRM
process-mapper
IncludedUse when a BizOps lead, COO, or process-improvement owner needs to document an end-to-end business process (procurement, employee onboarding, incident handoff, customer-onboarding, claims adjudication) in BPMN-style notation, measure cycle times by stage, surface where work spends most of its time waiting vs. being worked, and quantify the gap between processing time and total elapsed time. Pairs Lean / Six Sigma / Theory-of-Constraints canon with deterministic stdlib-only Python tools to produce a process map, a ranked bottleneck list (with severity + root-cause hypothesis), and a cycle-time analysis (P50, P90, value-add ratio, Little's-Law throughput). Distinct from sales-pipeline, system-reliability (SLO), and strategic-OKR work — this is tactical process documentation for internal operations.
payment-integration
IncludedIntegrate payments with SePay (VietQR), Polar, Stripe, Paddle (MoR subscriptions), Creem.io (licensing). Checkout, webhooks, subscriptions, QR codes, multi-provider orders.
customer-success-manager
IncludedMonitors customer health, predicts churn risk, and identifies expansion opportunities using weighted scoring models for SaaS customer success
sales-engineer
IncludedAnalyzes RFP/RFI responses for coverage gaps, builds competitive feature comparison matrices, and plans proof-of-concept (POC) engagements for pre-sales engineering. Use when responding to RFPs, bids, or proposal requests; comparing product features against competitors; planning or scoring a customer POC or sales demo; preparing a technical proposal; or performing win/loss competitor analysis. Handles tasks described as 'RFP response', 'bid response', 'proposal response', 'competitor comparison', 'feature matrix', 'POC planning', 'sales demo prep', or 'pre-sales engineering'.
customer-success-manager
IncludedMonitors customer health, predicts churn risk, and identifies expansion opportunities using weighted scoring models for SaaS customer success
sales-engineer
IncludedAnalyzes RFP/RFI responses for coverage gaps, builds competitive feature comparison matrices, and plans proof-of-concept (POC) engagements for pre-sales engineering. Use when responding to RFPs, bids, or proposal requests; comparing product features against competitors; planning or scoring a customer POC or sales demo; preparing a technical proposal; or performing win/loss competitor analysis. Handles tasks described as 'RFP response', 'bid response', 'proposal response', 'competitor comparison', 'feature matrix', 'POC planning', 'sales demo prep', or 'pre-sales engineering'.