Project API

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

GET/v1/integrations/stripe
POST/v1/integrations/stripe
PATCH/v1/integrations/stripe
DELETE/v1/integrations/stripe
POST/v1/integrations/stripe/sync

This 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
curl https://emailbump.com/api/v1/integrations/stripe \
  -H "Authorization: Bearer ebk_your_key"
200 response
{
  "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

connectedbooleanoptional

Whether the project has a Stripe account connected.

connectionobject | nulloptional

The connection, described below.

key_urlstringoptional

Stripe'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[]optional

The permissions that key asks for. What each is for is in the Stripe guide.

eventsarrayoptional

The contact events Stripe records, each with its name and key. The key is what a flow trigger or a wait_event step names.

attributesstring[]optional

The contact attributes Stripe keeps current.

The connection

connection fields

iduuidoptional

This connection. Connecting again makes a new one.

team_iduuidoptional

The project.

account_idstring | nulloptional

The Stripe account, acct_…. null when the key can't read the account.

account_namestring | nulloptional

The account's name in Stripe, when the key can read it.

livemodebooleanoptional

false for a test-mode key.

api_key_hintstringoptional

The 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_idstringoptional

The endpoint we created on the Stripe account, we_…, listed in Stripe under Developers → Webhooks.

create_contactsbooleanoptional

Whether a Stripe customer who isn't a contact becomes one.

last_event_atstring | nulloptional

When Stripe last sent an event, ISO 8601.

last_errorstring | nulloptional

The last event we couldn't process.

sync_statusstringoptional

The customer import: idle, running, done or failed.

sync_started_at, sync_finished_atstring | nulloptional

When the last import started and finished.

sync_errorstring | nulloptional

Why the last import failed.

sync_statsobject | nulloptional

What the last completed import found: customers, matched_contacts, created_contacts, subscriptions and charges.

connected_byuuid | nulloptional

The dashboard user who connected it; null when an API key did.

created_at, updated_atstringoptional

ISO 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_keystringrequired

A 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_contactsbooleanoptional

Whether 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
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.
400 — a key Stripe doesn't accept
{ "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_contactsbooleanrequired

Whether a Stripe customer who isn't a contact becomes one.

cURL
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
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:

409 — already running
{ "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
curl -X DELETE https://emailbump.com/api/v1/integrations/stripe \
  -H "Authorization: Bearer ebk_your_key"
200 response
{
  "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.