Stripe
Attribute Stripe Checkout payments and refunds to the visits that led to them.
Use this connector when your payments are created as Stripe Checkout Sessions (mode: payment or mode: subscription). Seean verifies each webhook with the endpoint’s signing secret, records the paid session as an order and matches later refunds to it. Attribution is derived on Seean’s server from the buyer’s tracked visits when the session metadata carries the visitor ID.
Quick connect
After Enable (step 1 below), skip steps 2–4: paste a restricted key (Developers → API keys → Create restricted key) with only Webhook Endpoints: Write into Quick connect under Stripe and choose Connect automatically. Seean creates the webhook for this website’s URL with checkout.session.completed, checkout.session.async_payment_succeeded, refund.created and refund.updated, saves its signing secret and shows Secret saved (ends in …). An older endpoint for the same Seean URL is deleted only after the new one is saved, so running quick connect again is safe; if the provider refuses that deletion the panel asks you to delete the old endpoint yourself. The key is used for that one request and is not stored; you can delete it afterwards. A test-mode key connects test mode and a live-mode key connects live mode. Seean keeps one Stripe connection per website, so use the key for the mode you take real payments in: a test or sandbox key replaces a live connection. If the key is wrong or lacks a permission, the panel shows the reason (including the provider’s own message, such as an endpoint limit) and nothing is saved. Saving a secret manually or choosing Disable while quick connect runs cancels it, and the endpoint it created is deleted. The manual setup below always works.
Setup
- In Seean open Settings → Integrations → Payment providers on your own website, choose Stripe, select Enable and copy the webhook URL. It has the form
https://events.seean.io/api/connect/stripe/<site ID>; use the exact value shown. - In the Stripe Dashboard open Developers → Webhooks (in Workbench: Webhooks), choose Add destination → Your account → Webhook endpoint and paste the URL.
- Select the events
checkout.session.completed,checkout.session.async_payment_succeeded,refund.createdandrefund.updated, then save the endpoint. - Choose Reveal next to the endpoint’s Signing secret (
whsec_…) and paste it into Seean, then choose Save secret. Test mode and live mode use different endpoints and secrets; connect the one that matches the checkouts you want to record. - Pass the attribution metadata described below when you create Checkout Sessions.
Only the website owner can connect, replace or disconnect a provider; one connection per provider; demo websites cannot connect providers.
Optional retained read key
Once Stripe is connected, only the website owner sees Optional Stripe payment reads below the webhook controls. Admins and viewers do not see these controls or the read-key account, mode or suffix. Read access is off until the owner explicitly enables it; the quick-connect key is never reused or retained for this purpose.
- Create a separate restricted read key (
rk_test_…orrk_live_…) in the same Stripe account and mode as the connected webhook. Allow reads of the account, Checkout Sessions, invoices and InvoicePayments, PaymentIntents, charges and refunds; webhook write access alone is insufficient. Permission labels and availability can depend on Stripe’s dashboard/API version. Seean validates access before saving; an unavailable resource or missing permission rejects the key. - Paste it into Restricted read key. Check the initially unchecked consent control authorizing Seean to retain it for exact initial Checkout invoices, payments and refunds. This does not include renewals or start a backfill.
- Choose Enable read access. The field clears after success and the status shows enabled, test/live mode, account and key suffix when available. The full retained key is never returned to the panel or prefilled. To replace it, paste a new key and give consent again, then choose Replace read key.
A failure appears beneath the controls and does not confirm a new key was saved; correct the key/permissions and retry explicitly. Controls are disabled while a request runs and on demo websites. Switching providers clears the typed read key and consent. Remove read key stops future authenticated reads without disconnecting the webhook or deleting existing orders or payment links. You can also revoke the key in Stripe; then future lookups needing it fail until the owner replaces or removes it.
Quick or manual reconnect removes retained read access, including when changing test/live mode. Enable it separately again with fresh consent. Quick connect remains ephemeral. Existing webhook endpoints must manually add refund.updated in Stripe; new quick-connect endpoints include it. No read-key setting updates an existing endpoint’s selected events.
Read access creates links only after a newly delivered signed paid Checkout event or an exact replay matching the stored order. Enabling it alone does not scan historical orders, apply old refunds or start background recovery. This guide describes the source implementation; production rollout and live restricted-key permission verification are not confirmed here.
Attribution handoff
The tracker exposes window.seean.attribution() with visitorId, landingPath, convertingPath and campaign. Read it in the browser when the visitor starts checkout, send it to your server with the checkout request and attach it as Checkout Session metadata:
// Browser: collect attribution when the visitor clicks "Buy"
await window.seean?.ready;
const a = window.seean?.attribution?.() ?? {};
const seean = { seean_visitor_id: a.visitorId, seean_landing_path: a.landingPath, seean_converting_path: a.convertingPath, seean_campaign: a.campaign };
// send `seean` to your server together with the checkout request
// Server: create the Checkout Session with that metadata
const session = await stripe.checkout.sessions.create({
mode: "payment",
line_items: [{ price: "price_123", quantity: 1 }],
success_url: "https://example.com/thanks",
cancel_url: "https://example.com/pricing",
metadata: { ...Object.fromEntries(Object.entries(seean).filter(([, v]) => v)), seean_product: "Pro plan" },
});
| Key | Meaning |
|---|---|
seean_visitor_id |
Links the order to the buyer’s tracked visits; touches, first and last source, landing page and time to buy are derived from that history. |
seean_landing_path |
Used only when Seean has no tracked visit for the visitor in the 90 days before the order. |
seean_converting_path |
The page where checkout started. |
seean_campaign |
Overrides the campaign derived from the tracked history. |
seean_product |
Product name shown on the Revenue page. Without it Seean records “Stripe checkout” or “Stripe subscription”. |
The helper can be absent before the tracker loads or return an empty object when collection is disabled; send whatever is available. Orders without seean_visitor_id are recorded as Unattributed. Payment Links carry only the static metadata saved on the link, so per-visitor attribution needs a Checkout Session created by your server.
What is recorded
| Event | Result in Seean |
|---|---|
checkout.session.completed |
Order when payment_status is paid. mode: payment becomes a one-time order, mode: subscription a subscription order. |
checkout.session.async_payment_succeeded |
Same, for delayed payment methods such as bank debits. |
refund.created, refund.updated |
Newly deduct only a successful refund: direct payment-intent order, or an opt-in verified link to the initial subscription Checkout payment. |
The order ID is the session’s payment_intent (for subscriptions the subscription ID, then the session ID), the amount is amount_total and the order time is the session’s created time. Sessions that are not yet paid and every other Stripe event are acknowledged and ignored.
Refunds
Only succeeded refunds are newly deducted. pending, requires_action, failed and canceled refunds are not newly applied; refund.updated allows a pending refund to arrive again once it succeeds. Without read access, pending/requires-action deliveries return HTTP 503 with a connection warning to add refund.updated to the existing endpoint (or enable reads and retry), rather than silently acknowledging the pending event. The original pending payload cannot itself prove later success: upgrade the endpoint, explicitly use Quick connect with the current event set, or recover a verified successful refund through the Order API. With opted-in read access, Seean retrieves the refund’s current authoritative status instead of trusting an older event’s status. A currently pending/requires-action refund remains retryable (503), so a retry of the original creation event can recover it after settlement even on a legacy created-only endpoint. Recovery is limited by Stripe’s retry window; expired deliveries need explicit reconciliation or the Order API. This does not reverse or audit refunds already recorded before these checks.
A successful refund reduces the order by its amount; partial refunds are supported and each Stripe refund ID is applied once. Refunds lower net revenue in the period of the original order. Without read access, a refund uses the direct payment_intent order ID. A missing order returns HTTP 503 with Last event failed: Order not found for up to one day after refund creation, then is acknowledged and ignored. Provider retries can recover an order that arrives later within that window.
For a mode: subscription session, the stored order normally uses sub_…, not the refund’s pi_…. Optional read access can prove the exact initial Checkout invoice’s payments and link those payments to the same order, preserving its ID, fingerprint, amount and attribution. It never guesses from a subscription’s latest invoice or links a renewal. A lookup outage, missing permission or incomplete proof is not evidence that a payment is unrelated; check the connector error and correct access before replaying. Unsupported or ambiguous payment arrangements cannot be automatically linked. Permanently unsupported proofs are acknowledged with HTTP 200 and a Last event failed warning for explicit review, not retried forever; 401/403/429, timeouts, unavailable reads and incomplete pagination remain retryable. Missing initial links also remain retryable beyond one day when reads are enabled.
Payment identity conflicts do not create a second order: if a proved initial payment already belongs to another recorded order, the Checkout insert and its links roll back together and the connection shows a conflict warning. A direct pi_… order is also refused when that payment already links to a preserved order, even without read access. Keep the original record and resolve the conflict; do not change IDs to bypass it. Optional read outages are different: the canonical paid Checkout may still be recorded without payment links, with a retryable warning; replaying the identical Checkout after access recovers adds links without duplicating that order. This does not clean up duplicates already created by earlier versions.
After Reset statistics, a replay whose original order timestamp predates the reset cannot recreate the removed order or payment links. Orders at the reset timestamp or later remain eligible; replaying an existing identical preserved order never inserts a second copy.
Without a proven link, use the Order API with the original order’s subscription ID as externalId and the Stripe refund ID as refundId, after verifying success yourself. Keep the existing order identifier; do not create a second pi_… order to bypass the mismatch. See delivery recovery if retries expire.
Explicit single-session reconciliation
A custom authenticated owner integration may call connectors.reconcileStripeCheckout with {siteId, sessionId, dryRun: true} for one exact cs_… session; this is not an Order API HTTP route or a control in the payment-provider panel. The action returns {linked, refunds, result}. Review the dry-run result first, then make a separate explicit call with dryRun: false only when you intend to apply it. Enabling read access never calls this action automatically.
The action proves a matching legacy order from its preserved canonical fingerprint, then conservatively adds proven initial-payment links and successful refund IDs. A fingerprint mismatch, conflicting link or ambiguous existing refund ledger requires review, not a new identifier. Existing IDs and fingerprints are not changed. Existing refund IDs, amounts and stored total must match the authoritative successful refunds before reconciliation can mark their provenance verified; mismatches remain for review and are never overwritten. With read access, a refund for an order whose recorded refunds are not yet verified (recorded before Seean marked the origin of each refund, through the Order API, or with a stored total that differs from its ledger) is not deducted on trust, even when the recorded IDs resemble re_…. Seean first retrieves the successful refunds of that refund’s payment. When every recorded refund is a distinct ID with the same amount among them and the stored total equals the ledger, the new refund is deducted once. The existing entries are marked verified only when no other successful refund of that payment is missing from the ledger; otherwise the order keeps being checked on each later refund, and a missed refund must still be recovered through the Order API. Any mismatch, a recorded refund that did not succeed or belongs to another payment of the same order, a repeated refund ID, or a refunded total without a matching ledger returns Last event failed: Manual or legacy refund ledger requires review and leaves the order unchanged. Replaying an already recorded exact refund ID remains a no-op and needs no ledger check. An unverified ledger that gains an entry between Seean’s lookup and its write returns HTTP 503 so the provider retry checks the current ledger instead of reporting a mismatch, and a delivery that fails for another retryable reason does not mark the ledger verified. Without read access these checks do not run: a successful refund is applied to its direct payment_intent order as described above. This is bounded single-session recovery, not a site-wide backfill; no live reconciliation or historical cleanup is claimed here.
Dry-run and apply both validate new refund timestamps against the preceding 1,098 days and at most one minute of future clock tolerance before changing links or money. Exact boundaries are accepted; already recorded refund IDs remain no-ops even after they age out. A failed apply rolls back the entire single-Checkout batch. Preview validates the current state, not a reservation: later state changes or elapsed time can change eligibility before a separate apply call.
Currency
Orders are stored in the website’s reporting currency (Settings → Integrations → Revenue integration). amount_total is already in Stripe’s smallest currency unit, which matches Seean’s minor units. A session charged in another currency is acknowledged but not stored, and the reason (for example Order currency EUR does not match the website's reporting currency USD) appears under the provider.
Verify
Complete a test-mode checkout against a test-mode endpoint connected with its own secret, or use Send test event on the endpoint page (Stripe’s sample session is only stored when its payment_status is paid). The status line under Stripe shows the last event time and, if the last event could not be stored, the error. Then open Revenue for a range containing the order and check the amount, product and derived attribution. Duplicate deliveries return duplicate and change nothing.
Limits
- Seean verifies the
Stripe-Signatureheader (HMAC-SHA256 overtimestamp.body) and rejects deliveries whose timestamp is more than five minutes old; Stripe’s retries carry fresh signatures. - Subscription renewals are billed through invoices, not Checkout Sessions, and are not received or linked by optional reads. Report them with the Order API using the invoice or payment intent ID as
externalId. - Initial-payment proof supports at most 20 payment references, with fully completed lists bounded to five pages of 100 rows per list operation. Truncated lists are not proof. Refund listing during reconciliation has the same five-page bound; existing per-order refund limits still apply.
- Shared payments allocated across multiple invoices, credits/credit notes, overpayments, out-of-band/non-PaymentIntent payments, missing invoice/payment references and mismatched amounts/currencies are unsupported for automatic linking. A charge-only refund requires read access to prove its PaymentIntent; a refund with neither usable payment nor charge reference cannot be linked. No subscription-wide inference or automatic historical scan is performed.
- Request bodies above 64 KiB are rejected with 413. Events Seean does not handle are acknowledged with 200; rate limits return 429 and temporary ingestion failures return 503 so the provider can retry. See delivery recovery.
- Disable stops accepting events (the URL answers 404); delete the endpoint in Stripe so it stops retrying. Existing orders are kept.
Related: Revenue attribution, Order API and refunds.
Something unclear? Contact support.