Stripe

Stripe

Connect Stripe with a restricted, read-mostly key. Pulse imports paid Checkout Sessions and invoices, listens for new payments and refunds, and credits each sale to the visit behind it.

Connect Stripe#

  1. Create a restricted key

    In Stripe, open Developers, API keys, Create restricted key. Give it these permissions:

    ResourcePermissionWhy
    Checkout SessionsReadImport paid sessions.
    InvoicesReadImport subscription payments and renewals.
    ChargesReadRefund state of each sale, so refunds count even when the webhook can’t reach Pulse.
    Webhook EndpointsWriteLet Pulse register its own webhook.
  2. Paste it into Pulse

    Settings, Revenue, Connect, Stripe. The key starts with rk_live_. Pulse checks it can read sessions and invoices, imports the last 365 days, and registers the webhook.

  3. Optional: paste a webhook secret

    Only needed when Pulse cannot register the webhook itself, which happens when its address is not https (local setups). Create the endpoint in Stripe by hand and paste its whsec_… signing secret into the second field.

What is synced#

  • Checkout Sessions that are complete and paid, and Invoices that are paid. A session and the invoice it creates share one payment, so nothing is counted twice.
  • Amount is the total minus tax, in the order's currency. Free sessions are skipped.
  • Product is the first line item's description; country is the customer's address country; the email is stored only as a keyed one-way hash.
  • Kind: the first invoice of a subscription is a subscription sale, later invoices are renewals, everything else is one-time.
Webhook eventWhat Pulse does
checkout.session.completedStores the paid session.
checkout.session.async_payment_succeededStores delayed payments (bank debits) once they clear.
invoice.paidStores subscription payments and renewals.
charge.refundedMarks the payment refunded when the charge is fully refunded.

Attribution#

Nothing to do. When a visitor clicks a link to buy.stripe.com, the script adds client_reference_id=<visitor id> to the URL at click time, so links added after page load are covered too, and logs a checkout event. Pulse reads client_reference_id from the finished session. A link that reaches a Payment Link through another address (a redirect, a branded domain) can be marked with data-pulse-checkout="stripe".

Checkout Sessions you create#

Read the id in the browser and send it to your server. Then set it as client_reference_id or metadata.pulse_vid.

// browser: send the visitor id with your checkout request
const vid = window.pulse?.visitorId();
const res = await fetch('/api/checkout', {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({ vid }),
});

Renewals arrive as invoices. Pulse reads pulse_vid from the invoice or its subscription's metadata, so put it in subscription_data.metadata as shown to attribute renewals too. You can also set metadata.source as a fallback source name.

Refunds and currencies#

A fully refunded charge, or a dispute you lost, removes the sale. A partial refund reduces it by the refunded share. Test-mode payments are ignored. Connections made before October 2026 should reconnect Stripe once: the new webhook adds dispute events and uses Pulse's pinned Stripe API version (until then, the 2-minute sync still finds refunds). Amounts in other currencies are converted to US dollars at the payment day's rate (see currencies).

Common issues#

“Stripe rejected this key”
Use a restricted key created in Developers, API keys (it starts with rk_), not a publishable key.
“This Stripe key is missing a permission”
Edit the key in Stripe and add read access to Checkout Sessions, Invoices and Charges.
Sales have no source
The checkout was not tagged. Payment Links are tagged by the script on the page that links to them; sessions you create need the visitor id as shown above.
The connection says it needs attention
Open Settings, Revenue to read the error, fix the key, and click Sync now.