Test Stripe Connect Webhooks Locally (Connected-Account Webhooks on localhost)
Test Stripe Connect webhooks locally on localhost without deploying. Capture connected-account events, forward to your handler, and verify the Stripe-Signature.
You are building a Stripe Connect platform — a marketplace or SaaS that onboards merchants as connected accounts — and you need to see your handler react to a real payment_intent.succeeded or account.updated event that happened on one of your connected accounts. The problem is immediate: Stripe will only POST to a public URL, and your handler is running on localhost:8080. Stripe has no way to reach it.
The usual workarounds are painful. Deploying to staging for every code change is slow. Copying a sample payload from the docs into curl gives you a guess at the real request — and worse, it omits the one field that makes Connect different: the account field telling you which connected account the event belongs to. What you want is to test Stripe Connect webhooks locally — real connected-account events, hitting your local handler, with a URL that does not change every time you restart.
This guide focuses on the Connect-specific bits. For the base Stripe flow, see Receiving Stripe webhooks on localhost.
What makes Connect webhooks different
A regular Stripe webhook endpoint listens to events on your own account. A Connect endpoint listens to events on the connected accounts your platform manages. Three things change:
- Scope: the endpoint is configured to listen to "events on Connected accounts," so it receives events triggered by resources that live inside your connected accounts.
- The
accountfield: every connected-account event includes a top-levelaccountfield (for exampleacct_1A2b3C...) identifying which connected account the event came from. Your handler must read this to route the event to the right merchant. - A separate signing secret: the Connect endpoint has its own signing secret, distinct from any account-level endpoint you already have. Using the wrong secret is the single most common reason signature verification fails.
You only need one Connect endpoint for all of your connected accounts — Stripe does not require a separate endpoint per merchant.
Why testing Connect webhooks locally is tricky
Stripe sits on the public internet; your dev machine usually does not. It is behind a router, a corporate firewall, or both, with no public IP and no inbound ports open. So you need something in the middle: a public endpoint Stripe can hit, that relays each request down to your laptop without you opening a single firewall port. That is what Webhook Relay does — and unlike a random tunnel URL, the endpoint is stable, so you configure Stripe once and never touch it again.
Step 1: Inspect the real payload with Webhook Bin
Before you write any handler code, find out what a connected-account event actually looks like. Open the free Webhook Bin — no signup — and you get an instant public URL.
- Copy the Webhook Bin URL.
- In the Stripe Dashboard, go to Developers → Webhooks → Add endpoint.
- Paste the URL into Endpoint URL, add a description, then choose Listen to events on Connected accounts (this is what makes it a Connect endpoint).
- Select events — for example
payment_intent.succeeded,account.updated,payout.paid— and add the endpoint.
Trigger an action on a connected account (in test mode, complete a payment on a connected account, or update an account) and inspect the captured request in Webhook Bin: the full JSON body, the query string, and every header. Confirm you can see the top-level account field and the Stripe-Signature header. That account field is your proof the endpoint is scoped to Connect, not your platform account.
For more on this approach, see How to test webhooks and What is a webhook.
Step 2: Forward the events to localhost with the relay agent
Once you know the payload, route those same events into your local handler. Sign up for Webhook Relay, install the relay agent (CLI or Docker), and create a bucket — say stripe-connect. The bucket gives you a stable public input endpoint.
Start forwarding to your local server:
relay forward --bucket stripe-connect http://localhost:8080/webhook
The agent opens an outbound connection to Webhook Relay and streams every incoming request down to http://localhost:8080/webhook. Because the connection is outbound, there are no firewall ports to open and no public IP needed — this works from your laptop, behind a corporate proxy, or inside a Kubernetes cluster. Running in Docker? The same command works in the official webhookrelay/webhookrelayd image. Full details are in the localhost forwarding docs.
Now set the Connect endpoint's URL in Developers → Webhooks to your Webhook Relay endpoint (or create it there from the start, still with Listen to events on Connected accounts selected). Trigger a connected-account event and watch it arrive on localhost.
Stripe Connect-specific configuration and quirks
A few Connect details worth knowing:
- Pick the Connect scope, not "Your account": when adding the endpoint you must explicitly choose Listen to events on Connected accounts. An account-scoped endpoint will never see your connected accounts' events.
- One endpoint covers all merchants: the same Connect endpoint receives events for every connected account, distinguished by the
accountfield. Route on that field in your handler. - Grab the Connect endpoint's signing secret: open the endpoint in the Dashboard and reveal its signing secret (it starts with
whsec_). This is different from your account endpoint's secret — store it separately. - Test mode vs live mode: webhook endpoints and their signing secrets are per-mode. Set up the endpoint in test mode while developing, then add the live endpoint and its own secret before going live.
- Replay from Stripe: the Dashboard lets you resend any past delivery to your endpoint with the original payload and signature — ideal for re-running a real connected-account event against your local handler.
Step 3: Verify the Stripe Connect webhook signature
Stripe signs every request so you can confirm it really came from Stripe. It sends the digest in the Stripe-Signature header — an HMAC-SHA256 computed over the string timestamp.payload using the endpoint's signing secret. For a Connect endpoint, that means the Connect endpoint's signing secret, not your account endpoint's.
Your handler should recompute the HMAC over the raw body (the exact bytes received, before any JSON parser reshapes them), check the timestamp is recent, and compare in constant time before trusting the payload. The Stripe SDKs do this for you via Webhook.constructEvent / Webhook.constructEvent equivalents — just be sure you pass the Connect secret. Mixing up the account and Connect secrets is the classic cause of "signature verification failed."
To sanity-check the underlying HMAC, paste a captured body, your secret, and the received signature into the free HMAC signature verifier. For language-specific code and the common pitfalls (reading the body after a JSON parser has consumed it, timing-safe comparison, the timestamp tolerance), read Verify a webhook signature.
Replay and iterate
This is where local development gets fast:
- Replay from Stripe via the endpoint's resend action to re-run a real connected-account delivery against your handler.
- Replay from Webhook Relay — past requests are stored on your bucket, so you can resend a captured event without touching Stripe at all.
- Iterate on your handler by editing code and replaying the same delivery until your routing on the
accountfield behaves correctly. No deploys just to test a single code path.
Because the Webhook Relay endpoint is stable, you can stop and restart the agent, reboot your machine, or come back next week — the Stripe Connect configuration never needs to change.
Get started
- Inspect a real connected-account payload in the free Webhook Bin — no signup needed.
- Create a Webhook Relay account, install the agent, and run
relay forward --bucket stripe-connect http://localhost:8080/webhook. - Add a Stripe endpoint with Listen to events on Connected accounts, point it at the stable URL, trigger an event, and watch it hit
localhost.
You will be testing real Stripe Connect events against your local handler in a few minutes — no deploys, no open firewall ports, and a URL you configure exactly once.
