Stripe
1
Copy the webhook URL
In Highseat, open Site settings › Revenue and click Connect on the Stripe row. Copy the URL from the first step. It looks like
https://app.highseat.com/api/webhooks/stripe/… and belongs to this site alone.2
Add the endpoint in Stripe
In the Stripe Dashboard, open the Webhooks tab in Workbench (Developers › Webhooks, or dashboard.stripe.com/webhooks) and click Create an event destination.
- Select Your account.
- Choose the Snapshot payload format. Highseat reads the payment from the event itself, and a thin event carries only the object’s id.
- Select these six events:
checkout.session.completed,checkout.session.async_payment_succeeded,invoice.paid,refund.created,charge.dispute.funds_withdrawnandcharge.dispute.funds_reinstated. - Click Continue, select Webhook endpoint, and click Continue again.
- Paste the URL from Highseat as the Endpoint URL and create the destination.
3
Paste the signing secret
On the endpoint’s page in Stripe, reveal the signing secret and copy it. It starts with
whsec_. Paste it into the third step in Highseat and click Connect Stripe.What Stripe sends
- A one-time Checkout payment is recorded when the session is paid. A payment method that settles later, such as a bank transfer or SEPA Direct Debit, is recorded on
checkout.session.async_payment_succeeded, when the money arrives. - A subscription is recorded from
invoice.paid, for the first payment and every renewal. Any other paid invoice is recorded the same way. - A refund comes off the sale it belongs to.
- A dispute comes off the sale it belongs to when Stripe withdraws the disputed amount (
charge.dispute.funds_withdrawn). When you win the dispute and Stripe returns the funds (charge.dispute.funds_reinstated), the amount goes back on.
charge.dispute.funds_withdrawn and charge.dispute.funds_reinstated to the events it sends. The URL and the signing secret stay the same.
Product names for Checkout
Stripe’s Checkout webhook carries no line items, so a one-time Checkout sale shows its product as “Unknown”. A restricted API key lets Highseat read the line items and name the product. Invoices carry their lines already and need no key.- In the Stripe Dashboard, open the API keys page (dashboard.stripe.com/apikeys) and click Create restricted key.
- Name the key, set Checkout Sessions to Read, and leave every other permission at None.
- Click Create key and copy the key. It starts with
rk_live_. - In Highseat, click Add key under Restricted API key on the Stripe row, paste the key and click Save.
Polar
1
Copy the webhook URL
In Highseat, open Site settings › Revenue and click Connect on the Polar row. Copy the URL from the first step. It looks like
https://app.highseat.com/api/webhooks/polar/… and belongs to this site alone.2
Add the endpoint in Polar
In Polar, open your organization’s Settings › Webhooks and click Add Endpoint.
- Paste the URL from Highseat into the URL field.
- Leave the format on Raw.
- Generate a secret, or set your own, and copy it.
- Select these two events:
order.paidandrefund.created. - Create the endpoint.
3
Paste the webhook secret
Paste the secret into the third step in Highseat and click Connect Polar.
order.paid, and a refund comes off the order it belongs to. Polar sends no webhook event for a dispute, so Highseat does not see a chargeback on a Polar order. Polar’s sandbox is a separate organization with its own webhooks, and Highseat records its orders like live ones.
Pass the attribution id to your checkout
A webhook says what was sold. It cannot say which visit sold it, because the provider never sees your pages. The tracker gives each browser tab an attribution id, and your checkout carries it to the provider in the checkout’s metadata, under the keyattribution_id. The sale then joins the visit that opened the checkout.
Read the id in the browser with getAttributionId() from the highseat package:
getAttributionId() never makes a checkout hang. It resolves to undefined at once when the page has no tracker script or the script failed to load, and after 3 seconds when the script is still loading. Pass { timeout } in milliseconds to wait less, for example getAttributionId({ timeout: 500 }) on a page where the tracker has long loaded. On a site with the plain script tag, window.highseat.getAttributionId() returns the id once the tracker has loaded.
Where the id goes depends on the checkout:
Create the checkout on your server
A provider checkout is created on your server, with your secret key, so the id travels in two hops: the browser reads it and sends it with the checkout request, and the server writes it into the metadata.Browser
- Stripe
- Polar
app/api/checkout/route.ts
mode: "subscription" and pass the same object as subscription_data: { metadata }. Stripe Checkout runs on Stripe’s own page in the buyer’s browser, so Stripe reads the buyer’s IP address itself.How a sale finds its visit
The attribution id reaches the events of the tab that opened the checkout. Their sessions bring in every page view of the same visit, so a checkout opened in a new tab still credits the tab that arrived from a search engine. When your site callsidentify() for a signed-in buyer, every earlier session of that user within the lookback window joins too. A buyer who signed up from a Reddit post two weeks before paying in a direct visit then credits Reddit under first touch. Only a user id seen before the payment counts, so a later sign-in never changes a sale’s credit.
The Attribution card in Site settings › Revenue picks the Model (Last touch by default, or First touch, or Linear) and the Lookback (30 days by default, or 7, 14 or 90). A change applies to every past sale.
A provider sale and a tracker purchase
With a provider connected, its webhook is the authoritative record of a sale. Apurchase event from track() whose order_id matches the provider’s order id is left out of revenue totals, so connecting a provider never counts a sale twice. The order id is:
Send
purchase without revenue for a checkout a provider reports. Send revenue only for a checkout no provider reports, and then with an order_id. A tracker purchase with a matching order_id still lends the sale its session when the checkout passed no attribution id.
When a sale shows no source
getAttributionId() resolves to undefined when an ad blocker or a privacy browser blocks the tracker, or when the tracker has not loaded within the timeout. The checkout still opens and the sale is still recorded, with no visit to credit. It counts in your revenue totals but appears under no source, since a source is read from the page view that started a visit. A paid order with no attribution id and no tracker events from the buyer is this case, not a broken integration.
Status of a connected provider
Each provider’s row on the Payment providers card shows one line:
A failing row stays connected. The provider retries each delivery, and the next event that verifies turns the row healthy again.