Stripe
What It Does
Stripe feeds DURUM.ai in two complementary ways:
- Initial import through an API key: when you connect your key, DURUM.ai reads the last 90 days of your Stripe account (successful payments, refunds, subscriptions) so your dashboards do not start from zero.
- Signed webhooks in real time: every payment, refund or dispute is then received within seconds, as long as the Stripe signature is verified with the secret of the destination.
| Stripe event | Type in DURUM.ai | When it triggers |
|---|---|---|
payment_intent.succeeded, charge.succeeded | Payment | A payment is collected (both shapes of the same payment count once) |
invoice.paid | Payment | A Stripe invoice is paid (Stripe Invoicing) |
charge.refunded | Refund (negative amount) | A refund is issued |
charge.dispute.created, charge.dispute.updated, charge.dispute.closed | Dispute | A customer disputes a payment |
checkout.session.completed | Sale | A customer completes a purchase through Stripe Checkout (optional destination) |
What changed on September 14, 2026
Signing secrets are no longer configured by your account manager. You enter them yourself in Settings → Integrations → Stripe → Configuration. Without a secret, the signature cannot be verified and payments are rejected (401 response).
Fastest Way: Continue with Stripe
No API key, no webhook, no secret to copy. DURUM.ai connects through the official DURUM.ai app on the Stripe App Marketplace.
- In DURUM.ai, open Settings → Integrations → Stripe card → Connect.
- In the Connect Stripe window, click Continue with Stripe.
- On the Stripe screen, choose your Stripe account, review the access (all read-only: accounts, charges and refunds, disputes, events, payment intents) and click Install.
- You are sent back to DURUM.ai: the Stripe card shows Connected. New payments, refunds and disputes arrive within seconds.
| Stripe event | Type in DURUM.ai |
|---|---|
payment_intent.succeeded, charge.succeeded | Payment (both shapes of the same payment count once) |
charge.refunded | Refund |
charge.dispute.created, charge.dispute.updated, charge.dispute.closed | Dispute |
Good to know
- Read-only: DURUM.ai can never create, change or refund a payment in your Stripe account.
- One Stripe account per DURUM.ai account. A Stripe account already connected to another DURUM.ai account is refused.
- Already connected with an API key? Nothing changes, your current setup keeps working. The app is for new connections.
- The 90-day history import is not available yet with the app. Need it? Use the API key method below.
- To disconnect: in Stripe, Settings → Installed apps → DURUM.ai → Uninstall. DURUM.ai stops receiving events within a second.
Don't see Continue with Stripe? Use the API key method below: it gives the same result.
Before You Start
- Access to the Stripe Dashboard with the Developers menu (API keys and webhooks).
- Access to DURUM.ai with permission to edit integrations (Settings → Integrations).
- Your Business ID: it is shown in Settings → Integrations, section For your developer, and already inserted in the webhook URLs listed there.
Step-by-Step Connection
Step 1: connect your API key (import of the last 90 days)
- In DURUM.ai, open Settings → Integrations → Stripe card → Connect.
- In the Stripe Dashboard: Developers → API keys → Create restricted key.
- Name the key (for example "DURUM.ai").
- Enable Read on: Charges, Customers, Refunds, Subscriptions, Invoices. Leave everything else at None.
- Click Create key, copy it and paste it into the Stripe API key field.
- Click Connect. DURUM.ai validates the key with Stripe, stores it encrypted, then starts the 90-day import.
Accepted formats: a restricted key rk_live_… (recommended) or a secret key sk_live_…. Test keys (rk_test_…, sk_test_…) are accepted too.
You can paste the secrets at this step
The connection window has a Webhook Signing Secrets (optional) section with three fields (Payment, Sale, Refund). If you already created your destinations in Stripe, paste the secrets there; otherwise do it at step 3. Reconnecting the key never removes secrets already saved.
Step 2: create your webhook destinations in Stripe
In the Stripe Dashboard: Developers → Webhooks → Add destination (or Add endpoint depending on the interface version). Create one destination per line below. Replace YOUR_BUSINESS_ID with your Business ID.
Destination 1: Payments (required)
https://app.durum.ai/api/webhook/payment?bid=YOUR_BUSINESS_IDEvents to select: payment_intent.succeeded and charge.succeeded. If your customers pay Stripe invoices, add invoice.paid. Any other event sent to this URL is ignored (200 response, "ignored" status in Logs).
Destination 2: Refunds (required)
https://app.durum.ai/api/webhook/refund?bid=YOUR_BUSINESS_IDEvents to select: charge.refunded, charge.dispute.created, charge.dispute.updated, charge.dispute.closed.
Destination 3: Sales (optional)
https://app.durum.ai/api/webhook/sale?bid=YOUR_BUSINESS_IDEvent to select: checkout.session.completed. Create this destination only if you sell through Stripe Checkout and want a completed session to count as a sale (on top of the collected payment, received by destination 1).
The Business ID must be in every URL
It tells DURUM.ai which signing secret to use. A URL without bid= cannot be verified with your secrets: the event is rejected.
Step 3: paste the signing secrets into DURUM.ai
- In Stripe, open each destination you created: the Signing secret starts with
whsec_. Click Reveal and copy it. - In DURUM.ai: Settings → Integrations → Stripe → Configuration → Webhook signing secrets section.
- Three fields: Payments, Refunds, Sales (optional). Paste into each field the secret of its destination.
- Click Save secrets.
What you see next: "Saved" or "Not saved" next to each field. The secret itself is never shown again. To replace a secret, paste the new one; a field left empty keeps the current secret.
Each field receives the secret of ITS destination
The Payments destination secret pasted into the Refunds field cannot verify refunds: they will be rejected. Check the URL ↔ field match before saving.
Verify It Works
- In Stripe, open the Payments destination → Send test event → choose
payment_intent.succeeded→ send. - The response must be
200. A401 Invalid signatureresponse means the secret for this type is missing or does not match this destination. - In DURUM.ai, open the Logs page (main menu): the event appears with source
stripeand its status (processed, ignored, deduped). - Repeat with the Refunds destination (
charge.refunded).
Events Captured
| Destination | Accepted Stripe events | Result in DURUM.ai |
|---|---|---|
/api/webhook/payment | payment_intent.succeeded, charge.succeeded, invoice.paid, charge.refunded | Payment (amount, email, name, phone, product); charge.refunded is also accepted here as a refund |
/api/webhook/refund | charge.refunded, charge.dispute.* | Refund (negative amount) or dispute |
/api/webhook/sale | checkout.session.completed | Sale (session total amount) |
| Initial import (API key) | Successful charges, refunds, subscriptions created or canceled over 90 days | Historical sales, refunds and subscriptions, deduplicated by Stripe identifier |
Useful details:
- No double counting:
payment_intent.succeededandcharge.succeededof the same payment are tied to the same Payment Intent and count once. An iClosed transaction whose source is Stripe is ignored when your Stripe integration is active. - Attribution: DURUM.ai ties the payment to the lead by email and by phone. If you create Checkout sessions server-side, you can add the fields
client_key,product_key,visitor_id,fbclid,gclid,fbpandutm_*inmetadata: they survive even when the buyer pays with a different email address. - Toggleable types: in Configuration, the list of active events lets you exclude a type (Sales, Payments, Refunds, Subscriptions) without touching Stripe.
Troubleshooting
| Problem | Likely cause | Solution |
|---|---|---|
401 Invalid signature response in Stripe | Secret missing, pasted in the wrong field, or URL without bid= | Open the destination in Stripe, copy its Signing secret, paste it into the matching Configuration field, check the URL |
200 response but nothing in DURUM.ai | Event type not listened to by this URL | Check the selected events (for example invoice.paid, not invoice.created); "ignored" status in Logs |
| Message "Stripe is not connected for this client" when saving secrets | The API key has not been connected yet | Do step 1 first |
| Message "Invalid secret" | The secret does not start with whsec_ or contains a space | Copy it again from Stripe |
| Destination disabled in Stripe | Too many consecutive errors (often 401) | Fix the secret, then re-enable the destination in Developers → Webhooks |
429 error | Too many simultaneous requests | Stripe retries automatically |
| "Insufficient permissions" error when connecting the key | The restricted key does not read Charges, Customers, Refunds, Subscriptions, Invoices | Edit the key in Stripe and reconnect |
FAQ
Do I need to configure a signing secret?
Yes. Since September 14, 2026, a Stripe webhook without a verifiable signature is rejected. You enter the secrets yourself in Configuration; nobody else needs to handle them.
Why two required destinations and one optional?
Each destination has its own secret and its own event list. Payments and refunds are the two flows that drive your collected revenue. The Sales destination is only useful when Stripe Checkout is your point of sale and you want the sale counted at checkout time.
bid= or client_key=?
Both are recognized, but the app shows your URLs with bid= (your Business ID). Use those.
Can DURUM.ai make a payment or change my Stripe account?
No. The key is read-only, encrypted, and never shown again. Nothing is written to Stripe.
What happens if I reconnect my API key?
Secrets already saved are kept. Only the secrets you paste in the reconnection window replace the ones of their type.
I use Stripe Connect with several accounts
If the payload carries a connected account identifier, DURUM.ai finds your account through the one recorded when the key was connected. The bid= in the URL remains required for signature verification.
Quick Checklist
- [ ] Restricted API key connected (90-day import started)
- [ ] Payments destination created with
bid=and the eventspayment_intent.succeeded,charge.succeeded - [ ] Refunds destination created with
bid=andcharge.refunded+ disputes - [ ] Secrets pasted in Configuration, each in the field of its destination
- [ ] Stripe test event:
200response, event visible in Logs