The integration with no endpoint to page you about
Picture a Stripe integration that has no public URL. There is no /webhooks/stripe route, no HMAC check at the top of a handler, no load balancer rule forwarding traffic to a receiver you scaled and patched. Events from Stripe land on an Amazon EventBridge bus inside your own AWS account, and a Lambda picks them up. Nothing on your side listens on port 443 for a provider to POST to.
This is not a thought experiment. Stripe's event destinations let you choose where events go, and Amazon EventBridge and Azure Event Grid sit right next to the classic webhook endpoint as first-class options. WorkOS, and a lengthening list of platforms, offer variations of the same idea. The pitch is that you stop running the receiving half of a webhook system and let a cloud event bus do it.
It is a real improvement for a specific set of problems. It also quietly deletes some of the controls you were relying on and introduces failure modes that look nothing like a failed delivery. Before you migrate anything, get clear on what actually moves when the endpoint disappears.
An event destination is a routing choice, not a new protocol
In Stripe's model, an event destination is the thing that decides where a generated event ends up. You create one and pick a type: a webhook endpoint, Amazon EventBridge, or Azure Event Grid. The events themselves are the same objects you already know, carrying the same type, the same id, and the same versioned payload. What changes is the transport and, with it, the trust model and the operational surface.
Creating an EventBridge destination through the API looks like this. You name it, choose whether you want thin or snapshot payloads, list the event types, and hand over the AWS account and region that will receive them.
curl -X POST https://api.stripe.com/v2/core/event_destinations \
-H "Authorization: Bearer $STRIPE_SECRET_KEY" \
-H "Stripe-Version: 2026-08-26.preview" \
--json '{
"name": "prod-eventbridge",
"type": "amazon_eventbridge",
"event_payload": "thin",
"enabled_events": ["v1.billing.meter.error_report_triggered"],
"amazon_eventbridge": {
"aws_account_id": "012345678910",
"aws_region": "us-east-1"
}
}'
Notice what is not in that call: no URL, no shared secret, no signing key. That absence is the whole story, and it is where the interesting tradeoffs start.
No endpoint means nothing to sign
With a webhook endpoint, the entire security model rests on one thing: you recompute an HMAC over the raw body and compare it to the Stripe-Signature header in constant time. That is how you know the bytes came from Stripe and arrived unmodified. It is also a step people get wrong constantly, which is why so much of the webhook canon is about signature verification and the replay attacks it does not stop.
On EventBridge there is no Stripe-Signature header because there is no HTTP request to your infrastructure. Stripe delivers into a partner event source that AWS binds to your account. The trust boundary is now the AWS-side association and the IAM permissions on the bus, rather than an application-level MAC you check by hand. You cannot forge an event onto that bus without already holding credentials inside the account, which is a very different threat model from an open URL on the public internet.
That is genuinely less code and one fewer thing to botch. But do not read it as "security handled." The boundary moved from your handler to your cloud IAM configuration, and a permissive event bus policy is now the equivalent of a leaked signing secret. Whether that is a win depends on which one your team is better at getting right.
The seven-day window that disables the whole thing
Here is the failure nobody expects, because it happens before a single event flows. When you create an EventBridge destination, Stripe provisions a partner event source in your AWS account and then waits. You have to associate that source with an event bus within seven days. Miss the window and AWS automatically deletes the pending source, at which point Stripe automatically disables the destination and your only path forward is to create a new one.
Read that sequence again, because it is a silent, time-delayed outage baked into the setup flow. Someone creates the destination in a rush, means to finish the AWS side tomorrow, gets pulled onto something else, and a week later the whole thing has quietly deleted itself. No event failed, because none were ever delivered. This is the same shape as an endpoint that gets disabled after sustained failures: the integration is off, nothing is erroring, and you find out from a gap in your data.
So treat the association as part of the same task as the creation. If you provision with infrastructure-as-code, the bus association belongs in the same apply, and a destination stuck pending for more than a day should trip an alert.
The envelope you parse is not Stripe's envelope
When events arrive over EventBridge, they are not shaped the way your webhook handler expects. EventBridge wraps the Stripe event object inside its own structure. The Stripe payload you care about lives under detail, while the outer envelope carries EventBridge's own fields: a detail-type set to the event type, and a source that reads aws.partner/stripe.com/ followed by your destination's unique id.
{
"detail-type": "customer.created",
"source": "aws.partner/stripe.com/ed_61Pgt...",
"region": "us-west-2",
"detail": {
"id": "evt_1Orlfc...",
"object": "event",
"type": "customer.created",
"api_version": "2023-10-16",
"data": { "object": { "id": "cus_Ph9z...", "object": "customer" } }
}
}
Any code you already wrote to parse Stripe's top-level event now has to reach one level deeper. More usefully, that outer envelope is exactly what EventBridge rules match on, so you route by detail-type and source instead of branching on event.type inside a monolithic handler. Filtering and fan-out become configuration rather than a switch statement.
Ordering is exactly as broken as before
If you were hoping the event bus would fix delivery order, it does not. Stripe is explicit that it does not guarantee events arrive in the order they were generated. Create a subscription and you might see invoice.paid before customer.subscription.created, the same way a webhook endpoint would. The transport changed. The out-of-order reality did not.
Two rules carry over unchanged. Do not use the event's created timestamp to reason about order, because distinct events can share a whole-second timestamp. And track event id values to detect duplicates, because at-least-once delivery means you will occasionally see the same event twice. The idempotency work you would do for a webhook handler is identical here. The bus does not consume the event for you.
Retries move out of reach
Delivery reliability on EventBridge is Stripe's problem, which sounds like pure upside. Stripe retries for up to three days with exponential backoff in live mode, and you never write a retry loop or stand up a dead letter queue for the delivery leg. Good.
The catch is the flip side of that same coin: you cannot manually resend events to EventBridge. With a webhook endpoint you can hit resend in the dashboard when your consumer had a bad deploy and you want the last hour replayed. That escape hatch does not exist here. If your Lambda threw for an hour and Stripe already considers those events delivered to the bus, replaying them is on you.
So the reconciliation habit matters more, not less. The events API as a pull-based backstop is your recovery path when the push side has moved beyond your control. You keep the ability to re-read state from Stripe precisely because you gave up the ability to re-request delivery.
Thin events still make you go fetch
Event destinations support both snapshot events, which embed a point-in-time copy of the object, and thin events, which carry an id and little else. Thin events on the bus behave the way thin events over HTTP do: they tell you something changed and leave you to fetch the current state yourself. Stripe calls this hydration, and on EventBridge you wire it up with a rule that routes the thin event to a target which then calls the API.
This is a feature, not a tax. Fetching on notify gives you the freshest state and sidesteps a pile of staleness bugs. But it means the event bus is not the end of the pipeline. A thin event on EventBridge is a trigger, and the actual data still comes from an authenticated API call your target makes. If you were picturing "events arrive, data is complete, done," thin delivery is not that.
The events that need an answer do not fit
Most events are fire-and-forget, but a handful expect your system to answer, and those do not translate to a bus cleanly. The sharpest example is Stripe Issuing: issuing_authorization.request asks your code to approve or decline a card authorization in real time, synchronously, by responding to the request. You cannot subscribe to it on an EventBridge destination at all. Stripe tells you to use a webhook endpoint for it, because a bus that hands the event to a queue has no way to carry your decision back in the tight window the authorization needs.
There are softer edges too. You can subscribe to checkout_sessions.completed on EventBridge, but doing so will not drive the redirect behavior that embedded or hosted Checkout depends on. That still needs an endpoint. This is the same lesson as taking control of webhook response signals: the HTTP response is sometimes part of the protocol, not just an acknowledgement. When the provider is waiting to hear back from you, an event bus that only delivers one way is the wrong tool, and you keep a real endpoint for exactly those event types.
What you actually gain
Strip away the caveats and the wins are concrete. You delete the entire public ingress: no URL to expose, no TLS to terminate for this, no receiver to autoscale under a retry storm, and a meaningfully smaller attack surface because there is no internet-facing endpoint to probe. The whole family of SSRF and ingress hardening concerns for the receive path largely evaporates when the receive path is an IAM-scoped bus.
You also inherit the routing fabric for free. Once events are on EventBridge you fan them out to Lambda, Step Functions, SNS, or SQS with rules instead of code. The integration patterns that take real engineering behind a plain endpoint, and the queue-based decoupling you would otherwise assemble by hand, come as native primitives. For a team already living in one cloud, that is a lot of plumbing you no longer maintain.
What you trade away
The bill comes due as coupling. Your event ingestion is now wedded to one cloud's event bus and its permission model, its regions, and its pricing. Local development gets harder, because a stripe listen tunnel to localhost no longer mirrors production. Your dev loop has to account for a bus that only exists in AWS. Monitoring moves too: delivery health and dead-lettering are now read from CloudWatch and your target's logs rather than a provider's delivery dashboard, so the monitoring you set up has to watch the bus, not an HTTP success rate.
And the things the bus does not do for you remain your job. Deduplication, idempotency, ordering tolerance, and reconciliation all survive the migration intact. The event bus removes an entire class of infrastructure. It does not make your consumer correct. That part was always the hard part, and it still is.
When to reach for it
Reach for an event-bus destination when you already run in a single cloud, you want events fanned across several internal consumers, and none of your critical event types require a synchronous response. That is the sweet spot: you shed the receiver, gain native routing, and lose nothing you were actually using. A serverless-first team on AWS processing billing events is close to the ideal case.
Stay with a plain endpoint when you need synchronous responses, when you want provider-side manual replay, when you run across clouds or on-prem, or when a tunnel-based local dev loop is worth more than deleting the receiver. Plenty of teams will run both: a bus for the high-volume fire-and-forget stream, a small endpoint for the handful of events that still expect an answer. The endpoint was never the whole system. It was the easy half to see and the hard half to run, and the event bus is a bet that you would rather not run it.
Frequently asked questions
Do I still need to verify signatures if events come through EventBridge instead of a webhook endpoint? No, and there is nothing to verify because there is no HTTP request carrying a Stripe-Signature header. Trust is established when the Stripe partner event source is associated with a bus inside your own AWS account, and enforced by the IAM permissions on that bus. The security work does not disappear, it relocates: a permissive event bus policy becomes the equivalent of a leaked signing secret, so the control you have to get right is your cloud access configuration rather than a constant-time HMAC comparison in your handler.
Why did my EventBridge destination stop working before I ever received an event? Almost certainly the seven-day association window. When Stripe creates the destination it provisions a partner event source that you must associate with an event bus in AWS within seven days. If you do not, AWS deletes the pending source and Stripe automatically disables the destination, leaving you to create a new one from scratch. Because no event was ever delivered, nothing errors and nothing alerts, so the integration simply never turns on. Treat the AWS association as part of the same task as creating the destination, and alert on any destination that stays pending for more than a day.
Can I replay past events to an EventBridge destination the way I resend a failed webhook? Not from Stripe. Manual resend is a webhook-endpoint feature, and event-bus destinations do not offer it, so if your consumer was broken for an hour you cannot ask Stripe to redeliver that window to the bus. Recovery becomes your responsibility on the AWS side, using dead-letter queues and downstream replay, or by pulling the missed data back through the Stripe events API. This is exactly why keeping a pull-based reconciliation path matters more once delivery replay is out of your hands.
Does routing events through a cloud event bus fix out-of-order delivery? No. Stripe does not guarantee event order over any transport, so EventBridge can hand you a later event before an earlier one just as an HTTP endpoint can. Do not use the created timestamp to infer order, since distinct events can share the same whole-second value, and keep tracking event ids to catch the duplicates that at-least-once delivery produces. Your consumer still has to be idempotent and order-tolerant. The bus changes the plumbing, not the reality underneath it.