Stripe
Turn Stripe payments, trials and cancellations into contact events and attributes, and credit each payment to the email that earned it.
View as Markdown ↗What it does
Connect your project's own Stripe account and what your customers do there reaches their contact:
- Events. Every payment, failed charge, trial and cancellation becomes an event on the customer's contact, so an automation can start on it.
- Attributes. Each customer's plan, MRR and lifetime value are kept on their contact, for segments and for personalizing email.
- Revenue attribution. Every payment is credited to the email that earned it, so campaign and automation reports show what each one made.
Customers are matched to contacts by email address, ignoring case. Several Stripe customers at one address — a second checkout often makes a second customer — are one contact: their subscriptions and lifetime value are combined. When a customer's address changes in Stripe, later events go to the new one. A project connects one Stripe account; connecting again replaces the previous connection and removes its webhook endpoint. Only admins and owners can connect or disconnect it.
Connect Stripe
- 01Open Integrations
In the dashboard, go to Integrations and choose Connect Stripe.
- 02Create a restricted key
Open Stripe takes you to Stripe's form for a new restricted key, with the permissions below already ticked. Create the key there. To try it on test data first, switch Stripe to test mode before you create it.
- 03Paste the key
Paste it into the dialog and choose Connect. We check the key, create the webhook, and start importing your customers.
To connect, re-sync or disconnect from code instead, use the Stripe API.
The key asks for these permissions:
rak_customer_read,rak_subscription_read,rak_invoice_read,rak_charge_read,rak_product_read— read access to the data the events and attributes are built from, and to the invoice a refunded charge paid.rak_webhook_write— the one write permission. It lets us create a single webhook endpoint on your account that sends events back to us.rak_connected_account_read— used only to show your account's name on the Integrations page.
A restricted key starts rk_live_ or rk_test_. A full live secret key (sk_live_…) is refused — it can move money on your account — and so is a publishable key (pk_…). Test-mode keys work too: test payments arrive as events and update contacts, but are never credited to emails, so trying it out can't put test revenue in your reports.
The CLI and the MCP server take the same keys, so an agent can try the integration on test data with a sk_test_… key.
Before saving, we check the key can read customers, subscriptions, invoices, charges and products. If a permission is missing you see Stripe's own error, which names the permission.
The webhook
There is no webhook to set up by hand. We create the endpoint on your Stripe account ourselves, pinned to API version 2026-08-26.dahlia so a change to your account's default version can't change what we receive, and subscribe it to:
invoice.paid,invoice.payment_failedcheckout.session.completed,checkout.session.async_payment_succeededcustomer.subscription.created,customer.subscription.updated,customer.subscription.deleted,customer.subscription.trial_will_endcharge.refundedcustomer.updated— used only when an address changesinvoice.voided— used only to end a voided invoice's payment link
Every delivery is checked against its signature before we act on it. Stripe delivers at least once, so the same event can arrive twice; a duplicate is ignored rather than recorded again.
The key and the webhook's signing secret stay on our servers and are never sent back to your browser. You can delete the key in Stripe at any time — see Disconnect.
Existing customers
Connecting starts an import, and Sync customers now runs it again. It reads every subscription and successful charge on the account, and gives each Stripe customer who has an email address the contact attributes below.
It records no events and credits no revenue for past payments. A payment from last spring isn't something a flow should react to today, and crediting it would put months of revenue into today's report. Events start with the first one Stripe sends after you connect.
It also clears the stripe_* attributes from contacts who aren't customers of the connected account — after you connect a different account, say — so segments on them stop matching.
Customers who aren't contacts yet
Add Stripe customers who aren't contacts yet is on by default, and you can turn it off on the Integrations page. While it's on, a Stripe customer with no matching contact becomes a subscribed contact:
- Customers found by the import are added in bulk, like a CSV import: no notification for each one, and no contact-created automations.
- A new customer arriving by webhook is added like a contact created through the API, so contact-created automations start for them.
- A refund never adds a contact. It's still taken back from the email its payment was credited to, even after that contact is deleted.
Either way they count toward your plan's contact limit. With the setting off, only existing contacts are updated. Addresses on reserved documentation domains, such as example.com, are always skipped.
Events
Each of these is recorded on the customer's contact. The key is what a flow trigger or a Wait for event step names. Once Stripe is connected they are all in the flow trigger's event picker, even before the first one has happened.
Events
stripe_payment_succeededStripe payment succeededoptionalAn invoice is paid for more than zero, or a one-time Checkout or Payment Link payment completes. The amount is the event's value, with its currency. Properties include invoice_id, subscription_id, billing_reason, hosted_invoice_url and plan.
stripe_payment_failedStripe payment failedoptionalAn invoice payment fails. It carries no value, so it never counts as revenue; the amount is in the amount_due property. Properties also include attempt_count, next_payment_attempt and hosted_invoice_url.
stripe_subscription_startedStripe subscription startedoptionalA subscription starts without a trial, or an incomplete one becomes active.
stripe_trial_startedStripe trial startedoptionalA subscription starts in a trial. Carries trial_end.
stripe_trial_endingStripe trial endingoptionalStripe's advance warning, normally about three days before the trial ends.
stripe_trial_convertedStripe trial convertedoptionalA trial becomes an active, paid subscription.
stripe_subscription_canceledStripe subscription canceledoptionalA cancellation is scheduled, for the end of the period or a set date. The customer keeps access until then. Carries cancel_at, reason, feedback and comment.
stripe_subscription_endedStripe subscription endedoptionalThe subscription ends — Stripe reports it as deleted.
stripe_plan_upgradedStripe plan upgradedoptionalThe subscription's monthly value goes up. Carries previous_mrr and mrr.
stripe_plan_downgradedStripe plan downgradedoptionalThe subscription's monthly value goes down. Carries previous_mrr and mrr.
stripe_refundStripe refundoptionalA charge is refunded. The value is the amount newly refunded, as a negative number, so a revenue segment nets it out.
Subscription events also carry subscription_id, status, mrr, currency, billing_interval and plan.
stripe_subscription_canceled fires while the customer still has access, which is when an email can still change their mind. stripe_subscription_ended fires when the access stops.
Contact attributes
Kept current on every matching contact. Build segments on them, and use them in email as {{ contact.stripe_plan }} and so on.
Attributes
stripe_customer_idstringoptionalThe Stripe customer id, cus_….
stripe_statusstringoptionalThe best standing across all their subscriptions: active, past_due, trialing, unpaid, paused, incomplete or canceled. Absent for someone who has never subscribed.
stripe_planstringoptionalThe product name, or names when they pay for more than one.
stripe_billing_intervalstringoptionalmonth, year, and so on.
stripe_mrrnumberoptionalMonthly recurring revenue in major units — 29.0, not cents. Other intervals are converted to monthly, so a yearly price is divided by 12. Only active, trialing and past-due subscriptions count, and only those in stripe_currency; metered and tiered prices count as 0.
stripe_ltvnumberoptionalLifetime value: successful charges minus refunds, in major units, in stripe_currency. Charges in other currencies aren't added in.
stripe_currencystringoptionalThe currency those amounts are in.
stripe_trial_endstringoptionalWhen the trial ends, as an ISO 8601 timestamp. Present only while they are trialing.
stripe_unpaid_invoice_urlstringoptionalSet when a payment fails, to the invoice's Stripe-hosted page, where they can pay it or update their card. Removed when that invoice is paid or voided.
Some segments worth having: stripe_mrr at least 99 for your largest customers, stripe_status is past_due for people whose card is failing, and stripe_status is canceled for people who have left. Numeric comparisons need the attribute to be a number, and stripe_mrr and stripe_ltv are.
A flow's emails render from the contact, not from the event that started the flow. So the link a failed-payment email needs lives on the contact, as {{ contact.stripe_unpaid_invoice_url }}.
Example: failed payments
An automation that chases a failed payment and stops once it's paid:
- 01Trigger
Start a flow on the event
stripe_payment_failed. - 02Send the link
Email them the invoice page,
{{ contact.stripe_unpaid_invoice_url }}, where they can pay or update their card. - 03Wait for payment
Add a Wait for event step on
stripe_payment_succeeded, with a timeout of a few days. - 04Remind
On the timeout path, send a reminder with the same link. If they pay first, the reminder never goes.
The same flow from code, with the Flows API:
curl -X POST https://emailbump.com/api/v1/flows \
-H "Authorization: Bearer ebk_your_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Failed payment",
"status": "draft",
"trigger": { "type": "event", "event_key": "stripe_payment_failed" },
"steps": [
{ "type": "send", "subject": "Your payment did not go through",
"html": "<p>We could not take your last payment. <a href=\"{{ contact.stripe_unpaid_invoice_url }}\">Pay the invoice or update your card</a>.</p>" },
{ "type": "wait_event", "event": "stripe_payment_succeeded", "timeout_days": 3,
"timeout": [
{ "type": "send", "subject": "Your invoice is still unpaid",
"html": "<p>A reminder: <a href=\"{{ contact.stripe_unpaid_invoice_url }}\">pay the invoice or update your card</a>.</p>" }
] }
]
}'It is created as a draft, so nothing sends until you activate it. Stripe retries a failed payment, and each failed retry is another stripe_payment_failed. The default re-enrollment rule, not_while_active, keeps someone from entering the flow a second time while they are still in it.
Revenue attribution
This applies to Stripe payments and to any event you send to the Events API with a value. With Stripe connected, don't also send the same purchases to the Events API — or send them with "attribute": false — or each one is credited twice.
When a contact's event carries a value above zero, the credit goes to the most recent click on a campaign or automation email inside your attribution window. If nothing was clicked and the project counts opens, it goes to the most recent human open. A click always beats an open, however recent the open. Automated opens — Apple Mail Privacy Protection, image proxies, security scanners — never count, and neither does a click on an unsubscribe link.
Only campaign and automation emails can earn credit. Transactional mail, such as a receipt or a password reset, never does, and neither do test sends.
The window is 5 days unless you change it. Set anything from 1 to 30 days under Settings → Revenue attribution, along with Credit opens too, which is on by default. A change applies to purchases from then on; revenue already credited stays where it is. Credit is decided when the purchase is recorded, so a click that reaches us after that — email providers report clicks within seconds — doesn't move it.
The Revenue API reads the credited totals from code and changes these settings.
Stripe payments
- A subscription's routine renewal is recorded — it is income, and it raises lifetime value — but it isn't credited to an email. No email earned this month's renewal.
- A customer's first payment is credited even when Stripe bills it as a renewal, as it does when a trial converts.
- Upgrades, proration invoices and one-time purchases are credited.
- A refund takes its amount back from the credit its own payment earned — matched through the invoice or Checkout payment it refunds, even when Stripe sends the refund first — never more than that payment earned. The debit is dated with the purchase, so every report that counted the purchase nets it out, and the purchase still counts as a conversion. Refunding a payment no email earned, like a renewal, takes nothing back.
- A payment your own code makes with a PaymentIntent, outside invoices and Checkout, isn't recorded as it happens. It reaches lifetime value at the next import. Send it through the events API if an email should get credit for it.
- Failed payments are never revenue.
- Test-mode payments are never credited.
Amounts in different currencies are never added together. Reports lead with the currency most purchases were in and list the others separately.
Where it shows
- Analytics — a Revenue tile, with revenue per delivered email, and a Top earning emails card listing campaigns and automations.
- Each campaign's Metrics tab.
- An automation's metrics, per send step on the canvas and in the side panel.
- Each contact's activity timeline — for example, “Credited to Spring sale (clicked before buying)”.
Disconnect
Integrations → Stripe → Disconnect removes our webhook endpoint from your Stripe account. If the key was already revoked we can't reach Stripe to do that, so delete the endpoint yourself under Developers → Webhooks in Stripe; the dashboard tells you when that's needed.
Contacts keep their stripe_* attributes and past events. Automations waiting on Stripe events stop receiving them.
Troubleshooting
The Integrations page shows when the last event arrived and, if one couldn't be processed, the error. Stripe retries a failed delivery for up to three days.
If the key was rolled or deleted in Stripe, events can't be processed. Disconnect, then connect again with a new key.
If a payment landed on the wrong contact, or on none, compare the email address on the Stripe customer with the contact's. That address is the only thing they are matched on.