# Payments not showing up

> Work through the usual causes: a failing signature, an unmatched visitor, test-mode noise, duplicates and refunds.

Section: Revenue attribution  
Canonical page: https://statsy.co/docs/revenue/troubleshooting  
Last updated: 2026-09-15

Almost every revenue problem is one of five things. Work down the list in order; the first two cover most of them.

## Nothing at all is arriving

Start in the provider, not in Statsy. Every provider keeps a log of webhook attempts and the response it got. Find the most recent attempt and read the status:

| Response | What it means |
| --- | --- |
| 404, site not found | The `ws_` key in the URL is wrong. Copy the URL from the connect dialog again. |
| 400, no integration configured | The URL is right but that provider was never connected, or was disconnected. Connect it and save the secret. |
| 400, signature verification failed | The signing secret does not match. See below. |
| 400, body must be valid JSON | Something between the provider and Statsy is rewriting the request body. A proxy, usually. |
| 413, payload too large | The webhook body exceeds 1 MB. Unusual; normally a provider sending an enormous line-item list. |
| 2xx, handled: false | It arrived and was understood, but that event type is not one Statsy records. Harmless. |

## Signature verification failed

- The commonest cause is a secret from the wrong place: an API key instead of a *webhook* signing secret, or the secret of a different endpoint.
- Watch for a trailing space or newline when pasting. Retype the last character if unsure.
- Regenerating the secret in the provider invalidates the one Statsy holds. Reconnect and paste the new one — the tile shows **Update secret** once a provider is connected.
- Stripe, Paddle and Polar also sign a timestamp, and Statsy allows five minutes of skew. If those three fail while Lemon Squeezy and Razorpay pass, suspect a clock, or a tool replaying old requests.

## Revenue shows, but not by channel

The money is in the totals and the payments list, but the channel table is empty or short. That means the payments arrived without a visitor. Common reasons:

- **The buyer was cookieless.** Visitors in the EU, UK and Switzerland have no persistent id in Auto mode, so nothing can be carried through checkout. Their email is the only route: call `statsy.identify()` when they sign up.
- **The checkout was created on your server** without `statsy_vid` being passed. See the code samples on your provider's page.
- **Checkout opened in a different browser** — an invoice link clicked days later on a phone, for example. Nothing can link the two except the email.
- **The visitor never had a source.** A payment credited to nothing and a payment credited to direct look similar; check the journey.

## Duplicates

You cannot really create them. Payments are unique per provider payment id, so a provider retrying after a timeout, or you replaying a week of webhooks during a migration, still stores one row. If a sale really does appear twice, they are two different payments — an initial charge and its first renewal, or a payment and a matching refund of the opposite sign.

## Test-mode money in a real dashboard

Statsy treats test and sandbox webhooks exactly like live ones; nothing marks them as fake. If you connect a test endpoint to your production site, those amounts count. Create a separate site in Statsy for sandbox testing, and point test-mode webhooks there.

## Refunds

A refund is a negative payment against the same channel as the sale, so revenue stays as it was and *net* revenue falls. If refunds look missing, check that you subscribed to the refund event as well as the payment one — they are separate subscriptions in every provider.

Still stuck? The payments list on the **Revenue** page shows what actually landed, with its provider and kind, which usually settles whether the problem is before or after Statsy.

Previous: [Attribution models and lookback](https://statsy.co/docs/revenue/attribution-models)  
Next: [The dashboard](https://statsy.co/docs/features)