@dodopayments/opencode-plugin 0.3.2 → 0.4.0

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,43 +1,65 @@
1
1
  ---
2
2
  name: dodo-best-practices
3
- description: Comprehensive guide for integrating Dodo Payments - the all-in-one payment and billing platform for SaaS and AI products.
3
+ description: Guide for initial Dodo Payments setup, including SDK installation, test and live environments, API keys, and the canonical checkout-to-webhook architecture.
4
4
  ---
5
5
 
6
6
  # Dodo Payments Integration Guide
7
7
 
8
- **Always consult [docs.dodopayments.com](https://docs.dodopayments.com) for the latest API reference and code examples.**
8
+ This skill covers the foundational concepts and setup for Dodo Payments. Use it when starting a new integration, setting up the SDK, or understanding the core payment flow.
9
9
 
10
- Dodo Payments is the all-in-one engine to launch, scale, and monetize worldwide. Designed for SaaS and AI products, it handles payments, billing, subscriptions, and distribution without extra engineering.
10
+ ## When to use this skill
11
+
12
+ - You're building a new payment integration and need to understand Dodo's architecture
13
+ - You need to install and initialize the SDK in your language
14
+ - You want to understand the canonical payment flow and webhook verification
15
+ - You're choosing between framework adapters or payment methods
16
+ - You need to know what Dodo handles versus what you must build
17
+
18
+ ---
19
+
20
+ ## What is Dodo Payments
21
+
22
+ Dodo Payments is a Merchant of Record (MoR). That means Dodo is the legal seller on every transaction, handles sales tax registration and calculation across all jurisdictions, remits taxes to authorities, and manages card-network disputes and chargebacks. As a developer, you don't build a sales-tax engine or dispute-handling system. You create checkout sessions, listen to webhooks, and grant access when payment succeeds.
11
23
 
12
24
  ---
13
25
 
14
- ## Quick Reference
26
+ ## Environment Setup
27
+
28
+ ### Base URLs
29
+
30
+ Only two real base URLs exist:
31
+
32
+ - **Live**: `https://live.dodopayments.com`
33
+ - **Test**: `https://test.dodopayments.com`
34
+
35
+ Never use `api.dodopayments.com` — it has no DNS record and cannot be reached.
36
+
37
+ ### API Keys
38
+
39
+ Keys have two formats:
40
+
41
+ - **Test**: `dodo_test_...`
42
+ - **Live**: `dodo_live_...`
15
43
 
16
44
  ### Environment Variables
17
- - `DODO_PAYMENTS_API_KEY` - Your API key from the dashboard
18
- - `DODO_PAYMENTS_WEBHOOK_SECRET` - Webhook signing secret for verification
19
45
 
20
- ### API Environments
21
- - **Live Mode**: `https://api.dodopayments.com` (default)
22
- - **Test Mode**: `https://api.dodopayments.com` with `environment: 'test_mode'`
46
+ ```bash
47
+ DODO_PAYMENTS_API_KEY # Bearer token for API requests
48
+ DODO_PAYMENTS_WEBHOOK_KEY # Secret for webhook signature verification
49
+ ```
23
50
 
24
- ### Dashboard URLs
25
- - **Main Dashboard**: [app.dodopayments.com](https://app.dodopayments.com)
26
- - **API Keys**: Dashboard → Developer → API
27
- - **Webhooks**: Dashboard → Developer → Webhooks
28
- - **Products**: Dashboard → Products
51
+ ### Default Environment
52
+
53
+ The `environment` parameter defaults to `live_mode` if omitted. Always pass `test_mode` explicitly during development.
29
54
 
30
55
  ---
31
56
 
32
- ## SDK Installation
57
+ ## SDK Installation & Initialization
58
+
59
+ ### TypeScript/Node.js
33
60
 
34
- ### TypeScript/JavaScript
35
61
  ```bash
36
62
  npm install dodopayments
37
- # or
38
- yarn add dodopayments
39
- # or
40
- pnpm add dodopayments
41
63
  ```
42
64
 
43
65
  ```typescript
@@ -45,271 +67,405 @@ import DodoPayments from 'dodopayments';
45
67
 
46
68
  const client = new DodoPayments({
47
69
  bearerToken: process.env.DODO_PAYMENTS_API_KEY,
48
- environment: 'live_mode', // or 'test_mode'
70
+ environment: 'test_mode', // defaults to 'live_mode'
49
71
  });
50
72
  ```
51
73
 
74
+ Constructor options:
75
+
76
+ | Option | Type | Default | Notes |
77
+ |---|---|---|---|
78
+ | `bearerToken` | string | env `DODO_PAYMENTS_API_KEY` | Required for API calls |
79
+ | `environment` | `'test_mode' \| 'live_mode'` | `'live_mode'` | Explicit in dev |
80
+ | `webhookKey` | string | env `DODO_PAYMENTS_WEBHOOK_KEY` | For `webhooks.unwrap()` |
81
+ | `baseURL` | string | — | Override; mutually exclusive with `environment` |
82
+
52
83
  ### Python
84
+
53
85
  ```bash
54
86
  pip install dodopayments
55
87
  ```
56
88
 
57
89
  ```python
90
+ import os
58
91
  from dodopayments import DodoPayments
59
92
 
60
- client = DodoPayments(bearer_token=os.environ["DODO_PAYMENTS_API_KEY"])
93
+ client = DodoPayments(
94
+ bearer_token=os.environ.get("DODO_PAYMENTS_API_KEY"),
95
+ environment="test_mode", # defaults to "live_mode"
96
+ )
61
97
  ```
62
98
 
63
99
  ### Go
100
+
64
101
  ```bash
65
- go get github.com/dodopayments/dodopayments-go
102
+ go get -u github.com/dodopayments/dodopayments-go@v1.110.0
66
103
  ```
67
104
 
68
105
  ```go
69
- import "github.com/dodopayments/dodopayments-go"
106
+ import (
107
+ "os"
108
+
109
+ "github.com/dodopayments/dodopayments-go"
110
+ "github.com/dodopayments/dodopayments-go/option"
111
+ )
70
112
 
71
113
  client := dodopayments.NewClient(
72
114
  option.WithBearerToken(os.Getenv("DODO_PAYMENTS_API_KEY")),
115
+ option.WithEnvironmentTestMode(), // defaults to live
73
116
  )
74
117
  ```
75
118
 
76
119
  ### PHP
120
+
77
121
  ```bash
78
- composer require dodopayments/client
122
+ composer require "dodopayments/client:6.19.0"
79
123
  ```
80
124
 
81
125
  ```php
82
126
  use Dodopayments\Client;
83
127
 
84
- $client = new Client(bearerToken: getenv('DODO_PAYMENTS_API_KEY'));
128
+ $client = new Client(
129
+ bearerToken: getenv('DODO_PAYMENTS_API_KEY') ?: 'My Bearer Token',
130
+ environment: 'test_mode',
131
+ );
132
+ ```
133
+
134
+ ### Ruby
135
+
136
+ ```bash
137
+ gem "dodopayments", "~> 2.22.0"
138
+ ```
139
+
140
+ ```ruby
141
+ dodo_payments = Dodopayments::Client.new(
142
+ bearer_token: ENV["DODO_PAYMENTS_API_KEY"],
143
+ environment: "test_mode"
144
+ )
145
+ ```
146
+
147
+ ### Java
148
+
149
+ ```xml
150
+ <dependency>
151
+ <groupId>com.dodopayments.api</groupId>
152
+ <artifactId>dodo-payments-java</artifactId>
153
+ <version>1.110.0</version>
154
+ </dependency>
155
+ ```
156
+
157
+ ```java
158
+ DodoPaymentsClient client = DodoPaymentsOkHttpClient.fromEnv();
159
+ // Reads DODO_PAYMENTS_API_KEY, DODO_PAYMENTS_WEBHOOK_KEY, DODO_PAYMENTS_BASE_URL
160
+ ```
161
+
162
+ ### Kotlin
163
+
164
+ ```xml
165
+ <dependency>
166
+ <groupId>com.dodopayments.api</groupId>
167
+ <artifactId>dodo-payments-kotlin</artifactId>
168
+ <version>1.110.0</version>
169
+ </dependency>
170
+ ```
171
+
172
+ ```kotlin
173
+ val client: DodoPaymentsClient = DodoPaymentsOkHttpClient.fromEnv()
85
174
  ```
86
175
 
87
176
  ---
88
177
 
178
+ ## Framework Adapters
179
+
180
+ Dodo publishes framework-specific packages under `@dodopayments/*`. Use the one matching your stack:
181
+
182
+ | Framework | Package | Use if |
183
+ |---|---|---|
184
+ | Next.js | `@dodopayments/nextjs` | Building with Next.js App Router |
185
+ | Nuxt | `@dodopayments/nuxt` | Building with Nuxt 3+ |
186
+ | Express | `@dodopayments/express` | Using Express.js |
187
+ | Fastify | `@dodopayments/fastify` | Using Fastify |
188
+ | Hono | `@dodopayments/hono` | Using Hono (Edge/Node) |
189
+ | Astro | `@dodopayments/astro` | Using Astro endpoints |
190
+ | SvelteKit | `@dodopayments/sveltekit` | Using SvelteKit server routes |
191
+ | Remix | `@dodopayments/remix` | Using Remix loaders/actions |
192
+ | TanStack Start | `@dodopayments/tanstack` | Using TanStack Start |
193
+ | Better Auth | `@dodopayments/better-auth` | Integrating with Better Auth |
194
+ | Convex | `@dodopayments/convex` | Using Convex backend |
195
+ | Bun | `@dodopayments/bun` | Using Bun.serve() |
196
+
197
+ Each adapter provides checkout, customer portal, and webhook handlers tailored to the framework's conventions.
198
+
199
+ ---
200
+
89
201
  ## Core Concepts
90
202
 
91
203
  ### Products
92
- Products are the items you sell. Create them in the dashboard or via API:
93
- - **One-time**: Single purchase products
94
- - **Subscription**: Recurring billing products
95
- - **Usage-based**: Metered billing per consumption
96
204
 
97
- ### Credit Entitlements
98
- Credits are virtual balances (API calls, tokens, compute hours) attached to products. Create them in Dashboard → Products → Credits:
99
- - **Custom Unit**: Your own metric with configurable precision
100
- - **Fiat Credits**: Real currency value (USD, EUR, etc.)
101
- - Attach up to 3 credits per product
102
- - Configure rollover, overage, and expiration per entitlement
205
+ Items you sell. Create in the dashboard or via API. Types:
206
+
207
+ - **One-time**: Single purchase
208
+ - **Subscription**: Recurring billing
209
+ - **Usage-based**: Metered consumption
210
+
211
+ ### Customers
212
+
213
+ Represent buyers. Can have multiple payment methods, subscriptions, and credit balances. Create explicitly or implicitly during checkout.
214
+
103
215
  ### Checkout Sessions
104
- The primary way to collect payments. Create a checkout session and redirect customers:
105
216
 
106
- ```typescript
107
- const session = await client.checkoutSessions.create({
108
- product_cart: [
109
- { product_id: 'prod_xxxxx', quantity: 1 }
110
- ],
111
- customer: {
112
- email: 'customer@example.com',
113
- name: 'John Doe',
114
- },
115
- return_url: 'https://yoursite.com/success',
116
- });
217
+ The primary payment collection method. Create a session server-side, redirect the customer to the hosted checkout URL, and listen for webhooks to confirm payment.
117
218
 
118
- // Redirect customer to: session.checkout_url
119
- ```
219
+ See the `checkout-integration` skill for detailed checkout configuration and the `subscription-integration` skill for recurring lifecycle management.
220
+
221
+ ### Subscriptions
222
+
223
+ Recurring charges on a schedule. Managed through checkout sessions or the subscriptions API. See the `subscription-integration` skill for lifecycle, trials, plan changes, and on-demand charging.
120
224
 
121
225
  ### Webhooks
122
- Listen to events for real-time updates:
123
- - `payment.succeeded` - Payment completed
124
- - `payment.failed` - Payment failed
125
- - `subscription.active` - Subscription activated
126
- - `subscription.cancelled` - Subscription cancelled
127
- - `refund.succeeded` - Refund processed
128
- - `dispute.opened` - Dispute received
129
- - `license_key.created` - License key generated
130
- - `credit.added` - Credits granted to customer
131
- - `credit.deducted` - Credits consumed
132
- - `credit.balance_low` - Credit balance below threshold
133
- ---
134
226
 
135
- ## Common Integration Patterns
227
+ Real-time event notifications. Dodo sends events like `payment.succeeded`, `subscription.active`, `refund.succeeded`, and `credit.deducted`. Webhook signature verification is covered in the `webhook-integration` skill.
136
228
 
137
- ### One-Time Payment Flow
229
+ ### Credit Entitlements
138
230
 
139
- 1. Create product in dashboard
140
- 2. Create checkout session with product ID
141
- 3. Redirect customer to checkout URL
142
- 4. Handle `payment.succeeded` webhook
143
- 5. Fulfill order / grant access
231
+ Virtual balances (API calls, tokens, compute hours) attached to products. Configured per product with rollover, overage, and expiration rules. See the `credit-based-billing` skill.
144
232
 
145
- ```typescript
146
- // Create checkout for one-time payment
147
- const session = await client.checkoutSessions.create({
148
- product_cart: [{ product_id: 'prod_one_time_product', quantity: 1 }],
149
- customer: { email: 'customer@example.com' },
150
- return_url: 'https://yoursite.com/success',
151
- });
152
- ```
233
+ ---
153
234
 
154
- ### Subscription Flow
235
+ ## The Canonical Payment Flow
155
236
 
156
- 1. Create subscription product in dashboard
157
- 2. Create checkout session
158
- 3. Handle `subscription.active` webhook to grant access
159
- 4. Handle `subscription.cancelled` to revoke access
237
+ 1. **Create checkout session** on your server with product ID and customer email.
238
+ 2. **Redirect customer** to the `checkout_url` returned.
239
+ 3. **Customer pays** on the hosted checkout.
240
+ 4. **Dodo sends webhook** (e.g., `payment.succeeded`) to your endpoint.
241
+ 5. **Verify the webhook signature** using `client.webhooks.unwrap()`.
242
+ 6. **Grant access** only after webhook verification succeeds.
243
+
244
+ Never grant access based on the browser `return_url` redirect alone. The webhook is the authoritative confirmation.
160
245
 
161
246
  ```typescript
162
- // Create checkout for subscription
247
+ import express from 'express';
248
+
249
+ const app = express();
250
+
251
+ // 1. Create session
163
252
  const session = await client.checkoutSessions.create({
164
- product_cart: [{ product_id: 'prod_monthly_subscription', quantity: 1 }],
165
- subscription_data: { trial_period_days: 14 }, // Optional trial
253
+ product_cart: [{ product_id: 'pdt_example', quantity: 1 }],
166
254
  customer: { email: 'customer@example.com' },
167
255
  return_url: 'https://yoursite.com/success',
168
256
  });
169
- ```
170
-
171
- ### Webhook Verification
172
257
 
173
- Always verify webhook signatures:
174
-
175
- ```typescript
176
- import crypto from 'crypto';
177
-
178
- function verifyWebhook(payload: string, signature: string, secret: string): boolean {
179
- const expectedSignature = crypto
180
- .createHmac('sha256', secret)
181
- .update(payload)
182
- .digest('hex');
183
-
184
- return crypto.timingSafeEqual(
185
- Buffer.from(signature),
186
- Buffer.from(expectedSignature)
187
- );
188
- }
258
+ // 2. Redirect to session.checkout_url
259
+
260
+ // 3. Listen for webhook with the exact raw request bytes
261
+ app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
262
+ try {
263
+ // 4. Verify signature
264
+ const webhookId = req.headers['webhook-id'] as string;
265
+ const event = client.webhooks.unwrap(req.body.toString(), {
266
+ headers: {
267
+ 'webhook-id': webhookId,
268
+ 'webhook-signature': req.headers['webhook-signature'] as string,
269
+ 'webhook-timestamp': req.headers['webhook-timestamp'] as string,
270
+ },
271
+ });
272
+
273
+ // 5. Suppress duplicates with an atomic unique insert and run side effects
274
+ // in the same database transaction.
275
+ const handled = await processWebhookOnce(webhookId, async () => {
276
+ if (event.type === 'payment.succeeded') {
277
+ // ONE-TIME purchases only. Subscription access starts on subscription.active.
278
+ const payment = event.data;
279
+ await grantOneTimeAccess(payment.customer.customer_id);
280
+ }
281
+
282
+ if (event.type === 'subscription.active') {
283
+ await grantSubscriptionAccess(event.data);
284
+ }
285
+ });
286
+
287
+ res.json({ received: true, duplicate: !handled });
288
+ } catch (error) {
289
+ res.status(401).json({ error: 'Invalid signature' });
290
+ }
291
+ });
189
292
  ```
190
293
 
191
294
  ---
192
295
 
193
- ## API Key Management
296
+ ## API Foundations
194
297
 
195
- ### Generation
196
- 1. Navigate to Dashboard → Developer → API
197
- 2. Click "Create API Key"
198
- 3. Copy and securely store the key
298
+ ### Authentication
199
299
 
200
- ### Security Best Practices
201
- - Never expose API keys in client-side code
202
- - Use environment variables
203
- - Rotate keys periodically
204
- - Use test mode keys for development
300
+ All requests use Bearer token authentication:
205
301
 
206
- ---
302
+ ```http
303
+ Authorization: Bearer dodo_live_...
304
+ ```
207
305
 
208
- ## Customer Portal
306
+ ### Pagination
209
307
 
210
- Allow customers to manage their subscriptions:
308
+ List endpoints are page-numbered. They accept `page_size` and `page_number`, and the response
309
+ exposes the rows on `items`:
211
310
 
212
311
  ```typescript
213
- const portal = await client.customers.createPortalSession({
214
- customer_id: 'cust_xxxxx',
215
- return_url: 'https://yoursite.com/account',
312
+ const payments = await client.payments.list({
313
+ page_size: 50,
314
+ page_number: 0,
216
315
  });
217
316
 
218
- // Redirect to: portal.url
317
+ for (const payment of payments.items) {
318
+ console.log(payment.payment_id);
319
+ }
219
320
  ```
220
321
 
221
- ---
322
+ The SDK can also walk every page for you:
323
+
324
+ ```typescript
325
+ for await (const payment of client.payments.list()) {
326
+ console.log(payment.payment_id);
327
+ }
328
+ ```
329
+
330
+ ### Rate Limits
331
+
332
+ Dodo enforces rate limits. The SDK automatically retries `429` responses as described below.
222
333
 
223
- ## Error Handling
334
+ ### SDK Error Classes
224
335
 
225
- Handle API errors gracefully:
336
+ The SDK throws typed errors. Catch and inspect:
226
337
 
227
338
  ```typescript
228
339
  try {
229
- const session = await client.checkoutSessions.create({...});
340
+ await client.checkoutSessions.create({...});
230
341
  } catch (error) {
231
- if (error.status === 400) {
232
- // Invalid request - check parameters
233
- } else if (error.status === 401) {
234
- // Invalid API key
235
- } else if (error.status === 429) {
236
- // Rate limited - implement backoff
342
+ if (error instanceof DodoPayments.APIError) {
343
+ console.error(error.status, error.message);
237
344
  }
238
345
  }
239
346
  ```
240
347
 
348
+ ### Retries
349
+
350
+ The SDK retries twice by default with a short exponential backoff on connection errors and `408`, `409`, `429`, and `5xx` responses. Do not add an unconditional custom retry loop.
351
+
352
+ Override the default for all requests when constructing the client:
353
+
354
+ ```typescript
355
+ const clientWithoutRetries = new DodoPayments({
356
+ bearerToken: process.env.DODO_PAYMENTS_API_KEY,
357
+ environment: 'test_mode',
358
+ maxRetries: 0,
359
+ });
360
+ ```
361
+
362
+ Or override it for one request:
363
+
364
+ ```typescript
365
+ await client.checkoutSessions.create(
366
+ {
367
+ product_cart: [{ product_id: 'pdt_example', quantity: 1 }],
368
+ customer: { email: 'customer@example.com' },
369
+ },
370
+ { maxRetries: 0 },
371
+ );
372
+ ```
373
+
241
374
  ---
242
375
 
243
- ## Testing
376
+ ## Webhook Verification
244
377
 
245
- ### Test Mode
246
- - Use test API keys (start with `sk_test_`)
247
- - Test webhooks with dashboard tools
248
- - Use test card numbers:
249
- - `4242 4242 4242 4242` - Success
250
- - `4000 0000 0000 0002` - Decline
378
+ Webhook signature verification is mandatory. Never trust the payload without verification.
251
379
 
252
- ### Local Development
253
- Use ngrok or similar for webhook testing:
254
- ```bash
255
- ngrok http 3000
380
+ Dodo implements the Standard Webhooks spec. The signed message is `webhook-id.webhook-timestamp.raw_body` (period-joined), HMAC-SHA256, base64-encoded.
381
+
382
+ **Use the SDK helper:**
383
+
384
+ ```typescript
385
+ const event = client.webhooks.unwrap(req.body.toString(), {
386
+ headers: {
387
+ 'webhook-id': req.headers['webhook-id'] as string,
388
+ 'webhook-signature': req.headers['webhook-signature'] as string,
389
+ 'webhook-timestamp': req.headers['webhook-timestamp'] as string,
390
+ },
391
+ });
256
392
  ```
257
- Then configure the ngrok URL as your webhook endpoint in the dashboard.
393
+
394
+ The `unwrap()` method verifies the signature and parses the payload. If verification fails, it throws an error.
395
+
396
+ For detailed webhook setup, event types, and testing, see the `webhook-integration` skill.
258
397
 
259
398
  ---
260
399
 
261
- ## Framework Integration
400
+ ## Common Mistakes
262
401
 
263
- ### Next.js
264
- Use API routes for server-side operations:
402
+ ### 1. Granting access on `return_url` redirect
403
+
404
+ The browser redirect is not proof of payment. Always wait for the webhook.
265
405
 
266
406
  ```typescript
267
- // app/api/checkout/route.ts
268
- import { NextResponse } from 'next/server';
269
- import DodoPayments from 'dodopayments';
407
+ // WRONG
408
+ app.get('/success', (req, res) => {
409
+ grantAccess(req.query.customer_id); // No verification!
410
+ });
270
411
 
271
- const client = new DodoPayments({
272
- bearerToken: process.env.DODO_PAYMENTS_API_KEY!,
412
+ // CORRECT
413
+ app.post('/webhook', async (req, res) => {
414
+ const event = client.webhooks.unwrap(...);
415
+ if (event.type === 'payment.succeeded') {
416
+ grantAccess(event.data.customer.customer_id);
417
+ }
273
418
  });
419
+ ```
274
420
 
275
- export async function POST(req: Request) {
276
- const { productId, email } = await req.json();
277
-
278
- const session = await client.checkoutSessions.create({
279
- product_cart: [{ product_id: productId, quantity: 1 }],
280
- customer: { email },
281
- return_url: `${process.env.NEXT_PUBLIC_URL}/success`,
282
- });
421
+ ### 2. Hand-rolling webhook verification
283
422
 
284
- return NextResponse.json({ url: session.checkout_url });
285
- }
286
- ```
423
+ Don't implement HMAC verification yourself. Use `client.webhooks.unwrap()`.
424
+
425
+ The old approach of signing just the payload is wrong because Standard Webhooks signs `webhook-id.webhook-timestamp.raw_body`. Hand-rolled HMAC will never match.
426
+
427
+ ### 3. Re-serializing the request body
428
+
429
+ Webhook verification requires the exact raw body. If you parse JSON and re-stringify it, the signature breaks.
287
430
 
288
- ### Express.js
289
431
  ```typescript
290
- import express from 'express';
291
- import DodoPayments from 'dodopayments';
432
+ // WRONG
433
+ const body = JSON.parse(req.body);
434
+ const event = client.webhooks.unwrap(JSON.stringify(body), {...});
292
435
 
293
- const app = express();
294
- const client = new DodoPayments({ bearerToken: process.env.DODO_PAYMENTS_API_KEY! });
295
-
296
- app.post('/create-checkout', async (req, res) => {
297
- const session = await client.checkoutSessions.create({
298
- product_cart: [{ product_id: req.body.productId, quantity: 1 }],
299
- customer: { email: req.body.email },
300
- return_url: 'https://yoursite.com/success',
301
- });
302
- res.json({ url: session.checkout_url });
303
- });
436
+ // CORRECT
437
+ const event = client.webhooks.unwrap(req.body.toString(), {...});
304
438
  ```
305
439
 
440
+ ### 4. Using deprecated APIs
441
+
442
+ Don't use `client.payments.create()` or `client.subscriptions.create()` for new integrations. Both are deprecated. Use `client.checkoutSessions.create()`.
443
+
444
+ ### 5. Forgetting to set `environment: 'test_mode'`
445
+
446
+ The default is `live_mode`. Always pass `test_mode` explicitly during development to avoid charging real cards.
447
+
448
+ ### 6. Storing API keys in code
449
+
450
+ Never hardcode keys. Always use environment variables.
451
+
452
+ ### 7. Ignoring the `webhook-timestamp` header
453
+
454
+ The timestamp prevents replay attacks. `client.webhooks.unwrap()` validates it automatically, but if you hand-roll verification, check that the timestamp is recent (within a few minutes).
455
+
456
+ ### 8. Using `unsafeUnwrap()` in production
457
+
458
+ `unsafeUnwrap()` skips signature verification. Use it only for unsigned test payloads from `dodo wh trigger`. Never use it for production webhooks.
459
+
306
460
  ---
307
461
 
308
462
  ## Resources
309
463
 
310
- - [Documentation](https://docs.dodopayments.com)
464
+ - [Dodo Payments Docs](https://docs.dodopayments.com)
311
465
  - [API Reference](https://docs.dodopayments.com/api-reference/introduction)
312
466
  - [SDK Repositories](https://github.com/dodopayments)
313
- - [Discord Community](https://discord.gg/bYqAp4ayYh)
314
- - [Support](mailto:support@dodopayments.com)
467
+ - [Checkout Sessions Guide](https://docs.dodopayments.com/developer-resources/checkout-session)
468
+ - [Webhook Guide](https://docs.dodopayments.com/developer-resources/webhooks)
469
+ - [Customer Portal](https://docs.dodopayments.com/features/customer-portal)
470
+ - [Subscription Integration](https://docs.dodopayments.com/developer-resources/subscription-integration-guide)
315
471
  - [Credit-Based Billing](https://docs.dodopayments.com/features/credit-based-billing)