Stripe
Connect Stripe with a restricted read-only key, which Stripe payments count, and passing the checkout reference to Stripe.
PilotFox reads a site's Stripe payments with a key that can only read, and links each one to the visit that led to
it. Connect it in Site settings → Revenue → Stripe, or with
PUT /api/v1/sites/{siteId}/integrations/stripe. The
revenue overview explains connecting, syncing, Match again and disconnecting for every provider.
Create a restricted key
In the Stripe Dashboard, open API keys and click Create restricted key. Give it Read access to the resources below and leave everything else at None.
| Resource | Access | Needed for |
|---|---|---|
| Checkout Sessions | Read | Required: the payments |
| Payment Intents | Read | Required |
| Charges and Refunds | Read | Required: refunds |
| Events | Read | Required: following new payments and refunds |
| Invoices (Billing) | Read | Optional: subscription renewals, and refunds of a subscription's first payment |
| Subscriptions (Billing) | Read | Optional: the subscription metrics |
PilotFox lists each required resource once when you connect. A key without one is refused with a message that names it; nothing is stored.
A secret key works too, but a restricted key can only read. Keys start with rk_live_ or rk_test_ (restricted) and
sk_live_ or sk_test_ (secret); publishable keys (pk_) cannot read anything and are refused. A test key imports
test-mode payments only. The settings show the key as its prefix and last four characters, such as rk_live_…wxyz.
Without Invoices or Subscriptions access everything else still imports, and the connection shows a notice saying what
is missing (the notice field of the connection). After adding a permission to the same key in Stripe (Edit key in
the key's menu), click Sync now: PilotFox checks again at once (otherwise every 6 hours).
When you Replace key and the site's latest Stripe payment is a subscription invoice, the new key also needs Read access to Invoices: PilotFox reads that invoice to check that the key belongs to the same Stripe account and mode. A key without it is refused with a message saying so.
PilotFox checks Stripe every 10 minutes once it has caught up (every minute while it imports), which keeps well inside the read allowance Stripe gives each account and that your own integration shares.
Which Stripe payments count
| Counted | Not counted |
|---|---|
Paid Checkout Sessions (status complete, payment_status paid), including those created by Payment Links | Payments made without Checkout or a subscription, for example PaymentIntents you create yourself, or one-off invoices |
| Payments by delayed methods, once Stripe marks them paid | Checkout Sessions that are unpaid, expired or have a total of 0 (free trials, setup mode) |
| Full and partial refunds of those payments | Unpaid, void or uncollectible invoices, and invoices of 0 (a trial's) |
| The first payment of a subscription started in Checkout | Renewals and their refunds, when the key cannot read Invoices |
| With Read access to Invoices: every paid subscription invoice (renewals, upgrades, prorations), with its refunds |
- A Checkout Session's payment id is the session's (
cs_…); a renewal's is its invoice's (in_…). A subscription started in Checkout counts its first payment once, as the Checkout Session; a subscription created without Checkout (through the API or the Dashboard) counts its first invoice instead. - The amount is the session's
amount_totalor the invoice'samount_paid, tax included. - A one-time payment's time is when it was charged. A subscription's first payment through Checkout keeps the time the Checkout Session was created; a renewal's time is when its invoice was paid.
Renewals are credited to the visit that started the subscription. Every payment of a subscription is linked like
its earliest linked payment (normally the first), affiliate included, so a renewal months later still counts for the
channel, campaign or affiliate that brought the subscriber. A subscription created without Checkout is linked through
the pilotfox_user_id (or pilotfox_ref) in its own metadata, which Stripe copies onto each of its invoices, or else
through the customer's email.
Pass the reference to Stripe
Set the checkout reference as metadata.pilotfox_ref, in
any format (Stripe metadata values are at most 500 characters). Metadata can be set on the Checkout Session
(metadata) or on its payment (payment_intent_data.metadata); PilotFox reads both, the session's first. For a
subscription, subscription_data.metadata reaches its invoices.
import Stripe from "stripe";
// A key that can create Checkout Sessions (not the read-only one you gave PilotFox):
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
app.post("/create-checkout", async (req, res) => {
const ref = String(req.body.ref ?? "");
const session = await stripe.checkout.sessions.create({
mode: "payment",
line_items: [{ price: "price_123", quantity: 1 }],
success_url: "https://example.com/thanks?session_id={CHECKOUT_SESSION_ID}",
metadata: { pilotfox_ref: ref.slice(0, 500) },
// A signed-in buyer: metadata: { pilotfox_ref: ref, pilotfox_user_id: user.id },
});
res.json({ url: session.url });
});The {CHECKOUT_SESSION_ID} in the success URL is optional: it lets the script link the payment from your
return page when nothing else does.
What PilotFox reads from a Stripe payment
| Field | Used as |
|---|---|
metadata.pilotfox_ref (session, then payment intent; invoice, then its subscription's metadata) | Checkout reference, any format |
client_reference_id | Checkout reference only in the PilotFox format (pf and digits); any other value is a user id |
metadata.pilotfox_user_id | User id |
The session id (cs_…) | Return-page id, matched up to an hour after the payment |
customer_details.email (else customer_email); on invoices customer_email | Customer email |
customer (cus_…) | Same-customer link for later payments |
subscription | The subscription link of renewals |
So client_reference_id may hold your own user id: it then links the payment to the user you identified with that id
(references and your own ids).
Stripe Payment Links
With Payment links and return pages on (the default), the script adds a fresh PilotFox reference to links to
https://buy.stripe.com/ as they are clicked, in their client_reference_id parameter, and sends the matching
checkout event (payment links).
To do it yourself, for example for Payment Links on your own domain: a Payment Link accepts the reference as its
client_reference_id URL parameter (Stripe accepts letters, digits, dashes and underscores, up to 200 characters).
Make it the way the script does, pf and digits; in any other format PilotFox reads it as a user id.
document.querySelectorAll('a[href^="https://pay.example.com/"]').forEach((a) => {
a.addEventListener("click", () => {
const ref = "pf" + crypto.getRandomValues(new Uint32Array(4)).join(""); // a PilotFox reference
window.pilotfox?.event("checkout", { ref });
const url = new URL(a.href);
url.searchParams.set("client_reference_id", ref);
a.href = url.toString();
});
});For the return page, set the Payment Link's redirect after payment to a URL of yours with
{CHECKOUT_SESSION_ID}, for example https://example.com/thanks?session_id={CHECKOUT_SESSION_ID}.
Stripe subscriptions
With Read access to Subscriptions, PilotFox reads every subscription for the
subscription metrics. Stripe's statuses are used as they are
(incomplete_expired counts as incomplete). The plan price is each licensed, per-unit recurring price times its
quantity, before tax and discounts; metered (usage-based) and tiered prices are left out.