@dodopayments/opencode-plugin 0.5.1 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: webhook-integration
3
- description: Complete guide for setting up and handling Dodo Payments webhooks for real-time payment event notifications.
3
+ description: Dodo Payments webhook handling, covering endpoint setup, Standard Webhooks signature verification on the raw body, payment, subscription, and refund event types, idempotency with webhook-id, retries, and local testing. Use when receiving Dodo events, verifying webhook-signature headers, or syncing your database from payment.succeeded or subscription events.
4
4
  ---
5
5
 
6
6
  # Dodo Payments Webhook Integration
@@ -272,7 +272,7 @@ Signature verification requires the exact bytes Dodo sent. If you parse the JSON
272
272
 
273
273
  ## Webhook Event Catalog
274
274
 
275
- Dodo sends 40+ event types across nine domains. Subscribe to only the events you need.
275
+ Dodo delivers 48 event types across ten families (payment, subscription, refund, dispute, license key, entitlement grant, credit, abandoned checkout, dunning, payout). Subscribe to only the events you need.
276
276
 
277
277
  ### Payment events
278
278
 
@@ -289,7 +289,10 @@ Dodo sends 40+ event types across nine domains. Subscribe to only the events you
289
289
  |-------|---|---|
290
290
  | `subscription.active` | Subscription becomes active; recurring charges are scheduled | Grant subscription access, send welcome email |
291
291
  | `subscription.updated` | Any field on the subscription changes | Sync changes to your database |
292
- | `subscription.on_hold` | Failed renewal temporarily pauses the subscription | Notify customer, prompt payment method update |
292
+ | `subscription.past_due` | Renewal failed and the grace period opened; customer keeps access (payload has `past_due_ends_at`) | Prompt for payment before the deadline |
293
+ | `subscription.on_hold` | Failed renewal stops renewals and revokes access | Notify customer, prompt payment method update |
294
+ | `subscription.paused` | Subscription is deliberately paused | Suspend access while paused |
295
+ | `subscription.unpaused` | Paused subscription is resumed | Restore access |
293
296
  | `subscription.renewed` | Subscription amount successfully deducted for a billing period | Log renewal, update next billing date |
294
297
  | `subscription.plan_changed` | Plan upgraded, downgraded, or modified | Update customer's access level or feature set |
295
298
  | `subscription.update_payment_method` | Payment method is updated | Sync the new payment method to your records |
@@ -320,14 +323,14 @@ Dodo sends 40+ event types across nine domains. Subscribe to only the events you
320
323
 
321
324
  | Event | When it fires | What to do |
322
325
  |-------|---|---|
323
- | `license_key.created` | License key is generated | Send key to customer (legacy; prefer `entitlement_grant.delivered`) |
326
+ | `license_key.created` | License key is generated | Legacy; prefer `entitlement_grant.created` with `status: "Delivered"` |
324
327
 
325
328
  ### Entitlement grant events
326
329
 
327
330
  | Event | When it fires | What to do |
328
331
  |-------|---|---|
329
- | `entitlement_grant.created` | Grant row is created | Prepare for fulfillment |
330
- | `entitlement_grant.delivered` | Fulfillment completes; customer receives access | Grant platform, file, or license-key access |
332
+ | `entitlement_grant.created` | Grant row is created. Auto-fulfilled license keys arrive here with `status: "Delivered"` and a `license_key`; no `.delivered` follows | Grant access when `status` is `Delivered`; otherwise prepare for fulfillment |
333
+ | `entitlement_grant.delivered` | An existing grant moves to `Delivered` (manual fulfillment, or a revoked grant restored) | Grant platform, file, or license-key access |
331
334
  | `entitlement_grant.failed` | Delivery fails and is no longer retried | Alert team, inspect `error_code` and `error_message` |
332
335
  | `entitlement_grant.revoked` | Access is withdrawn | Revoke customer access, inspect `revocation_reason` |
333
336
 
@@ -356,6 +359,18 @@ These concern virtual credit entitlements, not monetary wallet balances.
356
359
  | `dunning.started` | Dunning attempt begins after subscription enters `on_hold` | Monitor dunning progress |
357
360
  | `dunning.recovered` | Customer updates payment method and charge succeeds | Reactivate subscription, send confirmation |
358
361
 
362
+ ### Payout events
363
+
364
+ These concern your own funds moving to your bank account - use them for bookkeeping, not customer access.
365
+
366
+ | Event | When it fires | What to do |
367
+ |-------|---|---|
368
+ | `payout.created` | Payout is created (formerly emitted as `payout.not_initiated`) | Record the pending payout |
369
+ | `payout.in_progress` | Payout processing starts | Mark as in transit |
370
+ | `payout.on_hold` | Payout is paused or under review | Check whether more information is required |
371
+ | `payout.success` | Payout settles to your bank account | Reconcile in accounting |
372
+ | `payout.failed` | Payout fails; amount and fees return to your wallet | Alert finance, check bank details |
373
+
359
374
  ---
360
375
 
361
376
  ## Production-Grade Handler Pattern
@@ -464,7 +479,7 @@ Understand how Dodo delivers webhooks:
464
479
 
465
480
  | Property | Behavior |
466
481
  |----------|----------|
467
- | **Timeout** | 15 seconds for connection and read |
482
+ | **Timeout** | 30 seconds for connection and read |
468
483
  | **Success** | Any `2xx` response acknowledges delivery. Return `200` immediately after durably recording the event. |
469
484
  | **Failure** | Any non-`2xx` response triggers a retry. |
470
485
  | **Retries** | Eight attempts: immediately, 5s, 5m, 30m, 2h, 5h, 10h, 10h |
@@ -483,7 +498,7 @@ If you use a supported framework, use the official adaptor package for built-in
483
498
  |-----------|---------|---|
484
499
  | Next.js | `@dodopayments/nextjs` | `Webhooks({ webhookKey, onPayload })` |
485
500
  | Nuxt | `@dodopayments/nuxt` | `Webhooks({ webhookKey, onPayload })` |
486
- | Express | `@dodopayments/express` | Middleware with raw-body parser |
501
+ | Express | `@dodopayments/express` | `Webhooks({ webhookKey, onPayload })` - register `express.json()` first, **not** `express.raw()` (it verifies against the parsed `req.body`) |
487
502
  | Fastify | `@dodopayments/fastify` | `Webhooks({ webhookKey, onPayload })` |
488
503
  | Hono | `@dodopayments/hono` | `Webhooks({ webhookKey, onPayload })` |
489
504
  | Astro | `@dodopayments/astro` | `Webhooks({ webhookKey, onPayload })` |
@@ -549,7 +564,7 @@ Forward real test-mode events to localhost:
549
564
  dodo wh listen http://localhost:3000/webhook
550
565
  ```
551
566
 
552
- This creates a test webhook, opens a WebSocket relay, and forwards events with valid signatures to your local URL. Requires a test-mode API key. The URL argument is required in direct mode — bare `dodo wh listen` only works as `/wh listen` inside the TUI.
567
+ This creates a test webhook, opens a WebSocket relay, and forwards events with the original `webhook-id`/`webhook-signature`/`webhook-timestamp` headers to your local URL. Requires a test-mode API key. The relay and CLI parse and **re-serialize** the JSON body, so if it differs byte-for-byte from the original (for example number formatting), signature verification fails even though the headers are intact - that is a relay artifact, not a bug in your verifier. The URL argument is required in direct mode — bare `dodo wh listen` only works as `/wh listen` inside the TUI.
553
568
 
554
569
  ### CLI: Unsigned mock events
555
570
 
@@ -561,6 +576,8 @@ dodo wh trigger payment.success http://localhost:3000/webhook
561
576
 
562
577
  Use `unsafeUnwrap()` only for these unsigned payloads. Both arguments are required in direct mode.
563
578
 
579
+ Trigger names are not always the payload `type`: `payment.success` sends `type: "payment.succeeded"`, `refund.success` sends `refund.succeeded`, and `licence.created` sends `license_key.created`. Your handler must switch on the payload types. `subscription.past_due` and `subscription.unpaused` cannot be triggered.
580
+
564
581
  ### Tunnel
565
582
 
566
583
  Expose localhost with ngrok and register the HTTPS URL in the dashboard:
@@ -653,7 +670,7 @@ app.post('/webhook', async (req, res) => {
653
670
  await db.payment.create(...);
654
671
  await sendEmail(...);
655
672
 
656
- res.json({ received: true }); // Dodo times out after 15s
673
+ res.json({ received: true }); // Dodo times out after 30s
657
674
  });
658
675
  ```
659
676