Payments
Connect your payment provider to see completed payments and refunds in the Revenue dashboard. Matching tracked identities connect payments to their acquisition source.
TL;DR: Generate a webhook URL in the dashboard, add it to Stripe or Paddle, and pass Databuddy IDs in your payment metadata.
1. Generate your webhook URL
2. Add the webhook to your provider
In the Stripe dashboard, add an endpoint with your generated URL and subscribe to these events:
| Event | Purpose |
|---|---|
payment_intent.succeeded | Records successful one-time payments and payment context (required) |
checkout.session.completed | Links one-time Checkout metadata to its payment (required) |
checkout.session.async_payment_succeeded | Links Checkout metadata for delayed payment methods (required) |
invoice.paid | Carries the invoice metadata that links a payment to a visitor (required) |
invoice_payment.paid | Records the canonical payment for modern Stripe invoices (required on API 2025-05-28.basil or later) |
payment_intent.payment_failed | Tracks failed payment attempts (required) |
payment_intent.canceled | Tracks canceled payment attempts (required) |
invoice.payment_failed | Tracks failed invoice attempts and retries (required) |
charge.refunded | Records each refund (required) |
Databuddy expects Stripe API version 2025-05-28.basil or later, because invoice_payment.paid was introduced in that version and is the canonical money event for anything billed through an invoice. Keep payment_intent.succeeded enabled for one-time payments.
Checkout Session metadata stays on the Session unless you also set payment_intent_data.metadata. Keep checkout.session.completed and checkout.session.async_payment_succeeded enabled so Databuddy can link one-time Checkout metadata to its payment, including delayed payment methods. These events link metadata; payment events supply the revenue amount.
invoice.paid and invoice_payment.paid carry different halves of the same fact, and you need both. The payment event carries the amount but references its invoice by ID only, so it cannot see your metadata. The invoice event carries the metadata but no payment amount, because Stripe does not include the invoice's payments list in webhook deliveries. Databuddy records the amount from the payment event and joins the metadata from the invoice event, which is why a subscription payment stays unattributed if only one of the two is enabled. Order does not matter, and redelivering either event is safe.
Paste the endpoint's signing secret into the Revenue settings so Databuddy can verify each delivery.
In the Paddle dashboard, create a notification destination with your generated URL and subscribe to transaction.completed.
Paste the webhook secret key into the Revenue settings so Databuddy can verify each delivery.
3. Pass Databuddy IDs with each payment
Metadata connects a payment to a tracked visitor. Include the IDs you have: every field is optional, but an ID alone does not prove attribution. Databuddy must find a matching browser event on the same website at or before the payment.
import { getProfileId, getTrackingIds } from "@databuddy/sdk";
// In the browser, collect the visitor's IDs...
const { anonId, sessionId } = getTrackingIds();
const profileId = getProfileId();
// ...send them to your server and reuse the same metadata:
const databuddyMetadata = {
databuddy_client_id: websiteId,
databuddy_session_id: sessionId,
databuddy_anonymous_id: anonId,
databuddy_profile_id: profileId,
};
await stripe.paymentIntents.create({
amount,
currency,
metadata: databuddyMetadata,
});
// For recurring billing, attach it to the Subscription too.
await stripe.subscriptions.create({
customer,
items: [{ price: priceId }],
metadata: databuddyMetadata,
});Stripe does not automatically copy PaymentIntent metadata to future subscription invoices. Put the IDs on the Subscription (or subscription_data.metadata when using Checkout) so recurring revenue stays attributable.
import { getProfileId, getTrackingIds } from "@databuddy/sdk";
const { anonId, sessionId } = getTrackingIds();
Paddle.Checkout.open({
items,
customData: {
website_id: websiteId,
session_id: sessionId,
anonymous_id: anonId,
profile_id: getProfileId(),
},
});Databuddy first tries the payment's tracked session, then its profile ID, then its anonymous ID. If none matches, it can reuse a previously verified session for the same provider customer on the same website. Ambiguous sessions and sessions belonging to another identified profile are not credited. Stripe invoice context can supply IDs missing from a payment webhook.
Revenue attribution measures attributed completed-payment revenue divided by total completed-payment revenue in the selected currency. Refunds are reported separately. Profile revenue can show a payment associated with a user even when no browser event exists to attribute its acquisition source.
Verify it works
Send a test payment (Stripe test mode works), then check Website → Revenue. The transaction appears within seconds of the webhook delivery. If you passed a profile ID, the revenue also shows on that user in Website → Users.
Troubleshooting
How is this guide?