Stripe API
Connect a project's Stripe account from code, check on it, re-sync its customers, and disconnect it.
View as Markdown ↗Project key. Acts in the project the key belongs to. An all-access key works too — name the project with an X-Project-Id header. How keys and scopes work.
Endpoints
/v1/integrations/stripe/v1/integrations/stripe/v1/integrations/stripe/v1/integrations/stripe/v1/integrations/stripe/syncThis is the connection on the dashboard's Integrations page, and the two stay in step. What it records — the events, the contact attributes, and how payments are credited to emails — is in the Stripe guide. Every endpoint answers with the same object: the connection, or null, and what connecting provides.
Read the connection
GET /v1/integrations/stripe answers whether or not Stripe is connected.
curl https://emailbump.com/api/v1/integrations/stripe \
-H "Authorization: Bearer ebk_your_key"{
"connected": true,
"connection": {
"id": "3f1c9a7e-2b4d-4e8f-9a61-0c5d7b2e8f14",
"team_id": "111f9258-6b77-4edb-a9f3-14bc66118b14",
"account_id": "acct_1Example",
"account_name": "Acme",
"livemode": true,
"api_key_hint": "rk_live_…wxyz",
"webhook_endpoint_id": "we_1Example",
"create_contacts": true,
"last_event_at": "2026-09-28T14:02:11+00:00",
"last_error": null,
"sync_status": "done",
"sync_started_at": "2026-09-27T10:15:02+00:00",
"sync_finished_at": "2026-09-27T10:15:40+00:00",
"sync_error": null,
"sync_stats": {
"customers": 1840,
"matched_contacts": 1210,
"created_contacts": 630,
"subscriptions": 912,
"charges": 5230
},
"connected_by": null,
"created_at": "2026-09-27T10:15:01+00:00",
"updated_at": "2026-09-28T14:02:11+00:00"
},
"key_url": "https://dashboard.stripe.com/apikeys/create?name=Email%20Bump&permissions%5B%5D=rak_customer_read&permissions%5B%5D=rak_subscription_read&…",
"permissions": [
"rak_customer_read",
"rak_subscription_read",
"rak_invoice_read",
"rak_charge_read",
"rak_product_read",
"rak_webhook_write",
"rak_connected_account_read"
],
"events": [
{ "name": "Stripe payment succeeded", "key": "stripe_payment_succeeded" },
{ "name": "Stripe payment failed", "key": "stripe_payment_failed" },
{ "name": "Stripe subscription started", "key": "stripe_subscription_started" },
{ "name": "Stripe trial started", "key": "stripe_trial_started" },
{ "name": "Stripe trial ending", "key": "stripe_trial_ending" },
{ "name": "Stripe trial converted", "key": "stripe_trial_converted" },
{ "name": "Stripe subscription canceled", "key": "stripe_subscription_canceled" },
{ "name": "Stripe subscription ended", "key": "stripe_subscription_ended" },
{ "name": "Stripe plan upgraded", "key": "stripe_plan_upgraded" },
{ "name": "Stripe plan downgraded", "key": "stripe_plan_downgraded" },
{ "name": "Stripe refund", "key": "stripe_refund" }
],
"attributes": [
"stripe_customer_id",
"stripe_status",
"stripe_plan",
"stripe_billing_interval",
"stripe_mrr",
"stripe_ltv",
"stripe_currency",
"stripe_trial_end",
"stripe_unpaid_invoice_url"
]
}With nothing connected, connected is false and connection is null; the rest is the same.
Response fields
connectedbooleanoptionalWhether the project has a Stripe account connected.
connectionobject | nulloptionalThe connection, described below.
key_urlstringoptionalStripe's form for a new restricted key, with the permissions already ticked. Open it signed in to the Stripe account you want to connect, in test mode for a test key.
permissionsstring[]optionalThe permissions that key asks for. What each is for is in the Stripe guide.
eventsarrayoptionalThe contact events Stripe records, each with its name and key. The key is what a flow trigger or a wait_event step names.
attributesstring[]optionalThe contact attributes Stripe keeps current.
The connection
connection fields
iduuidoptionalThis connection. Connecting again makes a new one.
team_iduuidoptionalThe project.
account_idstring | nulloptionalThe Stripe account, acct_…. null when the key can't read the account.
account_namestring | nulloptionalThe account's name in Stripe, when the key can read it.
livemodebooleanoptionalfalse for a test-mode key.
api_key_hintstringoptionalThe key's prefix and last four characters, rk_live_…wxyz, to tell keys apart in Stripe. The key itself and the webhook's signing secret are never returned.
webhook_endpoint_idstringoptionalThe endpoint we created on the Stripe account, we_…, listed in Stripe under Developers → Webhooks.
create_contactsbooleanoptionalWhether a Stripe customer who isn't a contact becomes one.
last_event_atstring | nulloptionalWhen Stripe last sent an event, ISO 8601.
last_errorstring | nulloptionalThe last event we couldn't process.
sync_statusstringoptionalThe customer import: idle, running, done or failed.
sync_started_at, sync_finished_atstring | nulloptionalWhen the last import started and finished.
sync_errorstring | nulloptionalWhy the last import failed.
sync_statsobject | nulloptionalWhat the last completed import found: customers, matched_contacts, created_contacts, subscriptions and charges.
connected_byuuid | nulloptionalThe dashboard user who connected it; null when an API key did.
created_at, updated_atstringoptionalISO 8601.
Connect Stripe
POST /v1/integrations/stripe checks the key can read customers, subscriptions, invoices, charges and products, creates a webhook endpoint on the Stripe account — pinned to API version 2026-08-26.dahlia — and starts importing its customers in the background.
Body parameters
api_keystringrequiredA restricted key, rk_live_… or rk_test_…, made from key_url. A live secret key (sk_live_…) is refused: it can move money on the account.
create_contactsbooleanoptionalWhether a Stripe customer who isn't a contact becomes one. Defaults to true. See Customers who aren't contacts yet.
Any other field is a 400.
curl -X POST https://emailbump.com/api/v1/integrations/stripe \
-H "Authorization: Bearer ebk_your_key" \
-H "Content-Type: application/json" \
-d '{ "api_key": "rk_live_your_restricted_key", "create_contacts": true }'It answers with the object above, sync_status running. Read it again until the import is done or failed. The import sets the stripe_* attributes on each customer's contact; it records no events and credits no revenue for past payments. Events start with the first one Stripe sends after you connect.
A project has one Stripe connection. Connecting again replaces it, even with a different Stripe account, and removes the old webhook endpoint.
A refused key is a 400:
- A live secret key (
sk_live_…), a publishable key (pk_…), or anything that isn't a Stripe key. - A key Stripe doesn't accept — mistyped, rolled or deleted.
- A key missing a permission. The error is
Stripe refused the key:followed by Stripe's own message, which names the permission.
{ "error": "Stripe didn't accept that key. Check you copied all of it, and that it hasn't been rolled or deleted." }Change settings
PATCH /v1/integrations/stripe turns adding Stripe customers as contacts on or off. With it off, only existing contacts are updated. It is a 404 when Stripe isn't connected.
Body parameters
create_contactsbooleanrequiredWhether a Stripe customer who isn't a contact becomes one.
curl -X PATCH https://emailbump.com/api/v1/integrations/stripe \
-H "Authorization: Bearer ebk_your_key" \
-H "Content-Type: application/json" \
-d '{ "create_contacts": false }'It answers with the connection object.
Re-sync customers
POST /v1/integrations/stripe/sync runs the import again, like Sync customers now on the Integrations page: it re-reads every subscription and successful charge and refreshes each customer's contact attributes. It takes no body and answers with the connection object, sync_status running. Like the first import, it records no events and credits no revenue for past payments.
curl -X POST https://emailbump.com/api/v1/integrations/stripe/sync \
-H "Authorization: Bearer ebk_your_key"One import runs at a time; asking while one is running is a 409:
{ "error": "A sync is already running" }An import that has said running for more than an hour is taken to have died, and can be started again. It is a 404 when Stripe isn't connected.
Disconnect
DELETE /v1/integrations/stripe removes our webhook endpoint from the Stripe account and forgets the key. It is a 404 when Stripe isn't connected.
curl -X DELETE https://emailbump.com/api/v1/integrations/stripe \
-H "Authorization: Bearer ebk_your_key"{
"connected": false,
"connection": null,
"endpoint_removed": true,
"key_url": "https://dashboard.stripe.com/apikeys/create?name=Email%20Bump&…",
"permissions": [ … ],
"events": [ … ],
"attributes": [ … ]
}endpoint_removed is false when we couldn't reach Stripe to remove the endpoint — usually because the key was already revoked. Delete it yourself in Stripe under Developers → Webhooks; it is the webhook_endpoint_id the connection had.
Contacts keep their stripe_* attributes and past events. Flows waiting on Stripe events stop receiving them.