# Dodo Payments

Connect Dodo Payments with a live API key and a Standard Webhooks endpoint so ProductLense records payments, refunds and disputes net of deductions.

ProductLense reads your Dodo Payments customers, subscriptions, payments, refunds and disputes. Dodo Payments is a merchant of record. ProductLense only reads: it never writes to your Dodo Payments account, never refunds, cancels or changes anything.

## What you need

| Field | Secret | Where to find it |
|---|---|---|
| API key | Yes | A live API key from Dodo Payments → Developer → API Keys. |
| Webhook secret | Yes | The `whsec_...` secret shown on the webhook endpoint you point at ProductLense. |

Every plan includes one billing connection per site, Free too; Growth and Scale allow any number. See [plans and limits](/docs/account/plans-and-limits).

> [!NOTE]
> ProductLense connects to Dodo's live environment (`live.dodopayments.com`). Test mode keys do not validate.

## Permissions

Dodo Payments API keys are not scoped; ProductLense only reads with yours. It calls `GET /customers`, `/subscriptions`, `/payments`, `/payments/{id}` and `/refunds`.

## Set up

1. In Dodo Payments, open Developer → API Keys and create a live key.
2. In ProductLense, open your site's Settings → Billing connections → Dodo Payments, paste the key, check the Processor fees estimate, and click Connect and import history.
3. Copy the webhook URL from the connection card.
4. In Dodo Payments, open Developer → Webhooks, add an endpoint with that URL and subscribe to the payment, refund, subscription and dispute events below.
5. Copy the endpoint's secret into the connection in ProductLense.

## Webhook

URL: `https://api.productlense.app/api/webhooks/dodo/<connection-id>` (ProductLense shows the exact URL after you save).

Events ProductLense uses:

- `payment.succeeded`, `payment.failed`, `payment.processing`, `payment.cancelled`
- `refund.succeeded`, `refund.failed`
- every `subscription.*` event
- every `dispute.*` event (recorded as chargebacks)

Verification: Standard Webhooks headers (`webhook-id`, `webhook-timestamp`, `webhook-signature`). Deliveries older than 5 minutes are rejected.

## What ProductLense reads

- **Customers**: email, name and metadata, from customers and from the customer embedded in each payment.
- **Subscriptions**: status, interval and amount.
- **Payments**: payments, with their status.
- **Refunds and chargebacks**: refunds, netted against the refunded payment (fetched by its payment id), and disputes as chargebacks.
- **History**: the last 12 months on first connect, then a nightly reconcile at 03:00 UTC from the last sync minus 48 hours.

## How fees are derived

When the settlement is in the same currency as the payment:

```text
fee = total_amount − settlement_amount
```

That is Dodo's cut plus the tax it remits, so net revenue equals what reaches you. When the settlement currency differs from the payment currency, the two amounts cannot be subtracted, so the fee is the tax only and Dodo's platform cut is under-reported for that payment.

The connection starts with a fee estimate of 4% + $0.40 per payment, editable under Processor fees on the connection card (percent, fixed amount per payment, and its currency). The estimate is applied only to charges that arrive with a zero fee. A cross-currency payment that carries tax keeps the tax as its fee and does not get the estimate. Estimated fees are flagged, and the dashboard shows the share of fees in a period that is estimated. An estimate is only as accurate as the rate you enter.

## Match payments to visitors

ProductLense reads the user id from the customer's metadata and from the metadata on each payment and subscription: `pl_user_id`, `user_id`, `userId` or `uid`. Use the same id you pass to `identify`. A ProductLense visitor id in `pl_visitor_id` or `visitor_id` is also accepted. Without either, ProductLense falls back to the customer email, matched to the `email` trait sent with `identify`, or to a user id that is that email.

```json title="Checkout or payment request body, metadata part"
{ "metadata": { "pl_user_id": "user_123" } }
```

See [how payments are attributed](/docs/revenue/attribution).
