The Stripe webhook is required for Stripe payments to work with Off The Couch. Without it, the customer's card is charged in Stripe, but Off The Couch never finds out, the cart times out, and the customer sees:
Invalid transaction! Your cart has expired
If your booking site uses Stripe and customers are hitting that error after entering valid cards, the webhook is the first thing to check.
This article walks through the full setup in the Stripe Dashboard. Stripe's own reference is at docs.stripe.com/webhooks; this guide is the Off The Couch-specific version with the OTC URL, the right event selection, and the round-trip of pasting the signing secret back into OTC.
Why the webhook exists
Stripe processes the card asynchronously. Off The Couch needs to know the moment the charge succeeds (or fails, or is disputed) so it can mark the booking paid, send the confirmation email, and free the cart. The webhook is how Stripe tells Off The Couch. See Stripe: Receive webhook events for the underlying mechanism.
If the webhook isn't set up, every Stripe payment looks like a timeout to Off The Couch, even though the customer was charged in Stripe.
What you'll need
| Item | Where to get it |
|---|---|
| A Stripe account | stripe.com |
| Your Stripe API keys connected to OTC | Payment gateways (already configured if you've connected Stripe before) |
| Access to the Stripe Dashboard | dashboard.stripe.com |
You'll add two webhook destinations: one for test mode (for booking-site testing without real cards) and one for live mode (for real customer payments). The two are independent. If you only set up one, the other mode fails.
A note on Stripe's UI terminology
Stripe recently renamed "webhook endpoints" to event destinations as part of moving the developer tools into a new section called Workbench (which replaces the older "Developers Dashboard"). Both terms still appear in Stripe's docs; for our purposes, "destination" and "endpoint" mean the same thing. Stripe's reference: Trigger reactions in your integration with Stripe events.
Step-by-step
1. Copy the webhook URL from Off The Couch
| Step | Action |
|---|---|
| 1 | Go to Purchases > Payment settings |
| 2 | Switch to the Payment gateways tab |
| 3 | Confirm Stripe is the active gateway. If it isn't, set it up first (Payment gateways) |
| 4 | Scroll to the Webhook section (badge: important) |
| 5 | Click the copy icon next to the URL: https://api.offthecouch.io/stripe-webhook |
This URL is the same for every Off The Couch venue. Don't be alarmed; OTC routes incoming Stripe events to the right group based on the Stripe account that sent them.
2. Open the Stripe Workbench webhooks tab
| Step | Action |
|---|---|
| 1 | Open dashboard.stripe.com and sign in |
| 2 | Top-right toggle: pick Test mode or Live mode. Start with Test mode for the first run-through |
| 3 | Open Workbench (newer accounts) or Developers > Webhooks (legacy). Direct link in either: dashboard.stripe.com/webhooks |
| 4 | Click the Webhooks tab in Workbench |
| 5 | If you have older endpoints from prior setup attempts, you'll see them listed. If not, you'll see an empty state with the Add destination button |
Test mode and Live mode are completely separate worlds in Stripe. Webhooks added in Test mode only fire on test charges; webhooks added in Live mode only fire on real charges. You'll repeat this flow twice (once per mode). Stripe's note: Test and live mode.
3. Open the Create an event destination modal
| Step | Action |
|---|---|
| 1 | Click + Add destination within the Webhooks tab. |
| 2 | A modal titled Create an event destination opens. The left side shows a three-step stepper: Select events → Choose destination type → Configure your destination. You'll move through these in order |
If your dashboard still uses the older "Developers Dashboard" UI, the button is labeled + Add endpoint and there's no destination-type step (the only option was always a webhook endpoint). The rest of the flow is the same. Stripe reference: Add a webhook endpoint.
4. Step 1 of 3 — Select events
The right pane header reads Configure your event destination.
| Step | Action |
|---|---|
| 1 | Under Event destination scope, leave Your account selected (the default). This is the right choice unless you're using Stripe Connect with separate sub-accounts |
| 2 | Leave API version at its default unless Stripe instructs otherwise |
| 3 | Below Events, two tabs appear: All events and Selected events. Stay on All events |
| 4 | In the Find event by name or description... search box, type payment_intent |
| 5 | The list filters down to the Payment Intent group with 8 events. Click the arrow to expand it |
| 6 | Click the Select all Payment Intent events checkbox at the top of the group. All 8 events get checked |
| 7 | (Optional) Switch to the Selected events tab to confirm exactly 8 events are selected, all prefixed with payment_intent. |
| 8 | Click Continue at the bottom right |
You don't need any other event types. If you select more, OTC ignores them; if you select fewer than the full payment_intent.* set, some failure / cancellation paths won't propagate back to Off The Couch.
Stripe's full event catalog: Stripe API: Event types.
5. Step 2 of 3 — Choose destination type
The right pane header reads Choose where you want to send events.
| Step | Action |
|---|---|
| 1 | Three destination-type tiles appear: Webhook endpoint ("Send webhook events to a hosted endpoint"), Amazon EventBridge ("Send events to your AWS account"), and Azure Event Grid ("Send events to your Azure account") |
| 2 | Click Webhook endpoint (the other types aren't used by OTC) |
| 3 | Click Continue at the bottom right |
6. Step 3 of 3 — Configure your destination
The right pane header reads Configure destination with the subtitle "Tell Stripe where to send events and give your destination a helpful description." A summary block shows: Events from = Your account, Payload style = Snapshot, the chosen API version, and Listening to = 8 events.
| Step | Action |
|---|---|
| 1 | Destination name — type a short name like Off The Couch Platform |
| 2 | Endpoint URL — paste https://api.offthecouch.io/stripe-webhook (Stripe shows the helper text "Webhooks require a URL to send events to") |
| 3 | Description (optional but recommended) — type something like Off The Couch Platform integration so you can identify this destination later |
| 4 | Click Create destination at the bottom right |
| 5 | Stripe creates the destination and lands you on its detail page |
7. Copy the signing secret (recommended)
The signing secret is how Off The Couch confirms each incoming webhook is genuinely from Stripe and not a spoofed call. It's optional in the sense that OTC will accept unsigned webhooks, but strongly recommended for production: without it, anyone who learns your endpoint URL could submit fake webhook events. Stripe reference: Check the webhook signatures.
| Step | Action |
|---|---|
| 1 | On the destination's detail page, find the Signing secret section |
| 2 | Click Click to reveal to show the secret |
| 3 | Copy the secret. It starts with whsec_ and is followed by a long random string |
8. Paste the signing secret into Off The Couch
| Step | Action |
|---|---|
| 1 | Back in Payment settings > Payment gateways, scroll to the Webhook Secret section |
| 2 | Click Add webhook secret (the page shows "No webhook secret configured" with a warning icon if none is set yet) |
| 3 | Paste the whsec_... value into the Webhook endpoint secret field |
| 4 | Click Save |
| 5 | The page now shows Webhook signature verification enabled with a green checkmark |
9. Repeat for the other mode
If you started in Test mode, repeat steps 2-8 in Live mode (or vice versa). The signing secret is different for each mode; paste the right one into OTC for the mode you're currently using.
Important: If you switch the OTC payment gateway between test and live mode (using the live/test toggle on the Payment gateways tab), make sure the corresponding signing secret is in Webhook Secret. Otherwise signature verification fails and the webhook is rejected.
10. Verify with a test booking
| Step | Action |
|---|---|
| 1 | Switch your Stripe credentials in OTC to test mode (toggle on the Payment gateways tab) |
| 2 | On your booking site, start a new booking |
| 3 | At checkout, use a Stripe test card like 4242 4242 4242 4242 with any future expiration and any 3-digit CVC |
| 4 | Complete the booking. You should see the success screen, not "Invalid transaction! Your cart has expired" |
| 5 | In the Stripe Workbench, go back to your destination's detail page and check the Events tab. You should see payment_intent.succeeded and payment_intent.created with 2xx response codes |
If you see 4xx or 5xx responses, the webhook fired but Off The Couch couldn't process it. Check the Webhook signature verification state on OTC (most common cause: the signing secret is wrong or for the other mode).
Stripe's debugging reference: Webhook event delivery and retries.
11. Switch to Live mode for real payments
Once the test-mode flow works end-to-end:
| Step | Action |
|---|---|
| 1 | Confirm your Live-mode webhook destination is added in the Stripe Dashboard (separate from Test mode) |
| 2 | Confirm the Live mode signing secret is pasted into OTC's Webhook Secret field |
| 3 | Switch your OTC payment gateway to Live mode on the Payment gateways tab |
| 4 | Take a small real payment (your own card) as a final verification |
Reference
OTC webhook URL
https://api.offthecouch.io/stripe-webhook
Same URL for every venue, every mode. Stripe sends the events to OTC; OTC identifies your venue from the Stripe account ID on the event.
Required Stripe events
All events in the payment_intent group:
payment_intent.createdpayment_intent.succeededpayment_intent.payment_failedpayment_intent.canceledpayment_intent.processingpayment_intent.requires_actionpayment_intent.amount_capturable_updatedpayment_intent.partially_funded
Off The Couch only acts on a subset, but the easiest setup is "all payment_intent.* events." Stripe's catalog can change; the bulk-select pattern is future-proof. Full Stripe catalog: Stripe API: Event types.
Signing secret format
whsec_ followed by ~30-40 alphanumeric characters. Found in the Stripe Workbench under the destination's detail page, Signing secret card, Click to reveal.
OTC Webhook Secret states
| State | What you see in OTC |
|---|---|
| Not configured | Warning icon + "No webhook secret configured" + Add webhook secret button |
| Configured | Masked secret + Webhook signature verification enabled (green) + Edit / Remove buttons |
| Editing | Password input + helper text "Leave empty to disable signature verification (not recommended for production)" + Cancel / Save |
Stripe UI naming (current vs legacy)
| Concept | Current (Workbench) | Legacy (Developers Dashboard) |
|---|---|---|
| Where to find it | Workbench > Webhooks tab | Developers > Webhooks |
| Add button | + Add destination | + Add endpoint |
| What you're creating | Event destination (then pick Webhook endpoint type) | Webhook endpoint |
| Secret label | Signing secret > Click to reveal | Signing secret > Reveal |
Both flows produce a working webhook. Direct link to either: dashboard.stripe.com/webhooks.
Test mode vs Live mode
| Aspect | Test mode | Live mode |
|---|---|---|
| Stripe Dashboard toggle | Top right | Top right |
| Webhook destination | Separate destination in Test mode dashboard | Separate destination in Live mode dashboard |
| Signing secret | Different whsec_... value | Different whsec_... value |
| OTC gateway toggle | Test on Payment gateways tab | Live on Payment gateways tab |
| Cards accepted | Test cards only (4242 4242 4242 4242) | Real cards only |
Stripe documentation references
- Trigger reactions in your integration with Stripe events — Stripe's primary webhook guide
- Add a webhook endpoint — the specific section for adding a destination
- Stripe API: Event types — full catalog of available events
- Testing Stripe payments — test card numbers and scenarios
- Webhook signature verification — why the signing secret matters
- Webhook event delivery and retries — what happens when delivery fails
- Test and live mode — how the two modes differ
Good to know
- Webhook is required, not optional. The "important" badge in OTC is understating it. Without the webhook, Stripe payments will show "Invalid transaction! Your cart has expired" to the customer even though the card was charged. Setting the webhook up is part of going live with Stripe, not a nice-to-have.
- Test and live each need their own destination. Stripe treats Test and Live as separate accounts for webhook purposes (Stripe: Test and live mode). Doing it for one mode and not the other means the unconfigured mode fails.
- The signing secret is mode-specific. Test mode's secret is different from Live mode's secret. If you switch OTC between modes, swap the secret too.
- Stripe charges the customer even when the webhook is missing. This is the part that catches venues out: the customer sees "Invalid transaction!" and assumes the payment didn't go through, but in Stripe it did. Refund the orphan charge from the Stripe Dashboard if a customer reports double-paying.
- The endpoint URL is shared across all venues. OTC routes incoming events to the right venue using the Stripe account ID on each event. Don't be alarmed that the URL doesn't include your venue name.
- Pick
Webhook endpointfor destination type. The new Workbench flow asks what kind of destination you want (Webhook endpoint, Amazon EventBridge, Azure Event Grid, local listener). OTC only handles Webhook endpoint. The other types are for direct integrations with cloud providers and aren't relevant to OTC. - You only need
payment_intent.*events. Other event groups (charge, invoice, customer, etc.) aren't used by Off The Couch. Adding them doesn't break anything, but the bulk-select onpayment_intentis sufficient. - Disabled signing means anyone can fake events. If signing verification is off, the destination accepts events from any source that knows the URL. The URL isn't a secret, so production sites should always have a signing secret. See Stripe: webhook signature verification.
- Old endpoints can stay. If you have prior test destinations from earlier setup attempts, you can either delete them or leave them; OTC accepts events from any matching destination.
- Stripe retries failed deliveries. If OTC is temporarily down when a webhook fires, Stripe retries with exponential backoff for up to 3 days (Stripe: retries). The customer's booking eventually marks paid when delivery succeeds.
FAQ
Q: A customer paid in Stripe but their booking shows unpaid / shows "Invalid transaction." What do I do?
A: The webhook isn't reaching OTC. Two things to fix:
- Set up the webhook following the steps above (most common: it was never set up).
- While you're sorting that out, refund the orphan Stripe charge from your Stripe Dashboard so the customer isn't double-paid when they retry.
After the webhook is set up, ask the customer to try again; the new payment will register correctly.
Q: I added the webhook, but customers still see the timeout error.
A: Check the mode. If your OTC gateway is in Live mode but you only added the webhook in Stripe's Test mode dashboard (or vice versa), the modes don't match. Add the webhook in the matching mode and make sure the signing secret in OTC is from that mode's destination.
Q: I don't see "Workbench" in the Stripe Dashboard. What now?
A: Some accounts still use the older Developers Dashboard. In that case, navigate to Developers > Webhooks in the left sidebar, and click + Add endpoint instead of + Add destination. The rest of the flow is the same (URL, events, signing secret). Direct link works in either UI: dashboard.stripe.com/webhooks.
Q: Where in Stripe is the signing secret?
A: After creating the destination, you land on its detail page. Find the Signing secret section and click Click to reveal to show the secret. It starts with whsec_. Stripe's reference: Verify webhook signatures.
Q: What's the difference between a "destination" and a "webhook endpoint"?
A: Stripe's newer Workbench UI uses "destination" as the umbrella term for any place that receives Stripe events (webhook endpoint, Amazon EventBridge, Azure Event Grid, local listener). When you pick Webhook endpoint as the destination type, you're creating exactly the same thing the older UI called an "endpoint." Off The Couch only handles the webhook endpoint type.
Q: Can I use one webhook for multiple Off The Couch venues?
A: Yes, automatically. The webhook URL is the same for every venue, and OTC routes incoming events using the Stripe account ID on each event. If you have multiple groups with different Stripe accounts, each Stripe account needs its own webhook (added in that account's Stripe Dashboard).
Q: My webhook destination shows red exclamation marks or failed deliveries in Stripe.
A: That means recent delivery attempts failed. Click into the destination, open the Events tab, and click a failed event to see Off The Couch's response. Common causes: signing secret mismatch (401 in the response body), or the destination was added in the wrong mode (no matching OTC group).
Q: Should I add other Stripe events besides payment_intent?
A: Not currently. Off The Couch only acts on payment_intent.* events today. Adding others (e.g., charge.refunded) doesn't cause problems, but it also doesn't enable any extra behavior.
Q: Can I skip the signing secret to save time?
A: For test-mode setup, yes (no real money is at risk). For live mode, no. Without the secret, anyone who learns your webhook URL can submit fake payment_intent.succeeded events that mark bookings as paid without any real Stripe charge.
Q: My venue uses Connect / sub-accounts. Anything different?
A: Stripe Connect users should still add the destination to the main account, with Your account selected on the events screen (not Connected accounts). The Off The Couch handler isn't currently wired for Connect-only events.
Q: Where do I see what events have fired?
A: In the Stripe Workbench, open your destination's detail page and click the Events tab. Each row shows the event type, timestamp, and Off The Couch's HTTP response. 2xx is success; 4xx or 5xx indicates a problem on the OTC side. You can also use Stripe's event log search.
Q: I see a 401 response to webhook events.
A: The signing secret pasted in OTC doesn't match the one Stripe is signing with. Re-copy the secret from the Stripe Workbench (making sure you're in the right mode), paste it into the OTC Webhook Secret field, and save.
Q: I see a 5xx response to webhook events.
A: Off The Couch is having trouble processing the event. File a Support ticket with the event ID from the Stripe Workbench (visible on the event row) so we can look it up in our logs.
Q: I deleted my webhook destination by accident. Will Stripe re-create it?
A: No. Add a new one following this guide. Past payments that were already processed are unaffected (Off The Couch already received those events); but any pending payments that haven't completed yet won't reach Off The Couch until you re-create the destination. Stripe holds undelivered events in its retry queue for up to 3 days (Stripe: retries), so if you re-create within that window, in-flight events do eventually arrive.
Q: Is there an automated way to add the destination instead of doing it in the dashboard?
A: Yes, Stripe supports creating destinations via API (Stripe API: Event destinations) and CLI (Stripe CLI). For a single venue, the dashboard flow above is faster. For repeated setup across many test environments, the CLI is worth a look.