Skip to main content
Webhooks let Bachs push event notifications to your server the moment something happens: when a payment completes, a withdrawal fails, or a charge is disputed. You configure an endpoint URL and choose which events to subscribe to. We handle the delivery.

Prerequisites

  • A publicly reachable HTTPS endpoint on your server ready to accept POST requests.
  • Access to your Bachs developer portal.
Still building and have nothing deployed? Local testing forwards live events to a port on your machine, so you can write and debug your handler before you have a public URL.

Setup

You can manage webhook endpoints two ways: from the dashboard (below), or with the Webhook Endpoint API using an API key that has the webhooks:write scope. Both create the same endpoints with the same signing secrets.
In the dashboard, your role needs permission to manage webhooks. You need it to add, edit, enable, disable or delete an endpoint, to see or rotate its signing secret, and to resend events. The Owner, Super Admin and Developer roles have it by default. The Finance and Customer Support roles do not. API keys use permissions instead: webhooks:write for changes, and webhooks:read to read endpoints and signing secrets.
1

Open the Developer Portal

From your dashboard, click on your Developer Portal at the bottom leftA screen showing how to access the developer portal from the dashboard
2

Go to Webhooks

In the developer portal, navigate to the Webhooks section.A screen showing overview of the webhooks overview layout
3

Create a new endpoint

Click Add destination and enter the HTTPS URL where Bachs should deliver events. PS: All endpoints added automatically have a signing secret. We use it to sign every delivery so you can verify requests are genuinely from Bachs.A screen showing how to set up a webhook destination.
All webhook deliveries include an X-Bachs-Signature header. Validate it against the raw request body before processing the payload.
4

Select events

Choose the events you want to receive. You can subscribe to payment events, withdrawal events, or both. Only the events you select will be delivered to your endpoint.Once you save, your endpoint is live and will start receiving events immediately.

Verifying your webhooks

Every webhook delivery is signed. Before processing any payload, verify the signature to confirm the request came from Bachs.

Retrieve your signing secret

Each endpoint has an auto-generated signing secret. To find it:
1

Open the Developer Portal and go to Webhooks

Navigate to the Webhooks section in the developer portal.A screen showing the webhooks list in the developer portal
2

Open your endpoint

Click on the endpoint you want to verify deliveries for.A screen showing a webhook endpoint detail view
3

Copy the signing secret

The signing secret is displayed on the endpoint detail page. Copy it and store it securely in your environment variables.A screen showing the signing secret field on a webhook endpoint

How the signature works

Each delivery includes these headers: To verify, reconstruct the signed message using the timestamp and the raw request body, compute the HMAC-SHA256 using your secret, and compare it to the signature.
Prefer X-Bachs-Signature-V2 for new integrations. It carries the same digest, so the algorithm you implement is identical. It also names the scheme (v1=), which lets us introduce a new one later without breaking your verifier, and it can carry more than one signature, which is what makes rotating a secret safe. X-Bachs-Signature continues to be sent and is not going away without notice.
When verifying X-Bachs-Signature-V2, split on ,, take the t= value as the timestamp, and accept the delivery if any v1= value matches what you computed. Comparing against only the first one will fail during a rotation.

Rotating a signing secret

Rotating adds a new secret and keeps the previous one valid for 24 hours. During that window every delivery is signed with both, so traffic keeps verifying while you deploy:
  1. Rotate the secret and store the new value returned to you.
  2. Deploy it. Until you do, the old secret still matches.
  3. The old secret stops signing when the window closes.
If a secret is compromised and you need the old one dead immediately, rotate with a zero-hour overlap, but any endpoint still using the old secret will start rejecting deliveries at once.
Always read the raw request body before JSON parsing. Parsing the body first and re-serializing it can alter whitespace and byte order, which will break signature verification.

Verification examples


Receiving Events

Every webhook delivery is a POST request with a JSON body. The envelope looks like this:
Use the id field to deduplicate deliveries. We guarantee at-least-once delivery, so the same event may arrive more than once.
Ignore fields you don’t recognise. We may add new fields to the envelope or to data at any time, and we treat that as a backwards-compatible change. Parse leniently: read the fields you need and ignore the rest. Strict deserialization that rejects unknown fields will start failing when a field is added. That includes Go’s DisallowUnknownFields, Pydantic’s extra="forbid", and schema validation in front of your handler. Removing a field or changing the meaning of an existing one is a breaking change, and we will not do it silently.

Responding to deliveries

Return 400 only for a delivery you reject, such as one with an invalid signature. If your own processing fails, for example a database write, return a 5xx so the delivery is retried. See Retries for which responses are retried, and when. Events are not guaranteed to arrive in the order they happened, and a retried event can arrive after a newer one. Before you overwrite stored state, check that the event is not older than the state you already have, for example by comparing created_at.

Retries

A delivery succeeds when your endpoint returns any 2xx status within 10 seconds. Anything else is a failed try. What we do next depends on how it failed: Each delivery gets up to 5 tries: the first one, then retries about 1 minute, 5 minutes, 15 minutes and 1 hour after each failed try. The last try comes about 80 minutes after the first. After the fifth failure we stop. To send the event again, resend or replay it. A resend or a replay is a single try. We do not retry it.
Return a 2xx as soon as you have stored the event, and do the slow work after that. A handler that takes longer than 10 seconds counts as failed, even if it finishes the work.

When an endpoint keeps failing

If every delivery to an endpoint fails for days, we warn you and then turn the endpoint off. This is what happens:
  1. After 24 hours of failures, we email the account owner and admins. The email names the endpoint, says when it started failing, and gives the date we will turn it off.
  2. After 72 hours of failures, and at least 48 hours after that email, we turn the endpoint off and email the account owner and admins again. On the endpoint, enabled becomes false, disabled_at is set, and disabled_reason is consecutive_failures.
“Failures” means every delivery failed, with no success in between. One successful delivery resets the count. Every failed try counts, including the ones we do not retry, such as a 401. You always get the full 48 hours after the warning, even when the endpoint has already been failing for 72 hours. We only check the times when a delivery fails, so an endpoint that receives no events in that window stays on.

While an endpoint is off

  • We send it no new events.
  • Retries that were already waiting stop.
  • Events that happen while it is off are not sent later, even after you turn it back on. They are still recorded on your account, so you can resend them.

Turn it back on

Fix the cause first: check that the URL is reachable and returns a 2xx. If it still fails after you turn it on, the 24-hour count starts again from the next failure.
Open the endpoint from Webhooks in the developer portal. An endpoint we turned off shows a notice with the date we turned it off. Click Enable endpoint on the notice, or turn on the Enabled switch.A screen showing a webhook endpoint we disabled, with the notice and its Enable endpoint buttonThe endpoint list shows each endpoint’s status: Enabled, Disabled, or Disabled automatically when we turned it off.A screen showing the webhook endpoint list with Disabled and Disabled automatically statuses
Turning an endpoint on clears its failure history, disabled_at and disabled_reason. Then send the events it missed. List your events, keep the ones created after disabled_at whose type the endpoint subscribes to, and resend each one to the endpoint. Your handler should already ignore an event id it has seen, so a duplicate does no harm.
Note disabled_at before you turn the endpoint back on. Turning it on sets disabled_at to null, and you need it to know which events to resend.

Turn an endpoint off yourself

Send { "enabled": false } to Update a webhook endpoint, or turn off the Enabled switch on the endpoint page in the dashboard and confirm. A screen showing the Enabled switch on a webhook endpoint's details The rules in While an endpoint is off apply. We do not email you, and disabled_at and disabled_reason stay null, so you can tell your choice from ours.

Connect events

If you run a Connect platform, an endpoint can also receive events that happened on your connected accounts. Each endpoint carries an event_source:
An endpoint created without setting event_source receives nothing about your connected accounts. Set it explicitly when you want Connect events.
An event happens on one account, its origin. The origin’s own endpoints receive it on account or all, and the origin’s parent receives it on connect or all. Delivery walks up one level and no further, so an event never reaches a sibling account. On an event from a connected account, organization_id is that connected account rather than your platform, and a top-level account field carries the same id. Read the account from the payload rather than assuming the event belongs to the account you authenticated as.
account is absent on your own account’s events, so its presence tells you an event came from a connected account.

Event reference

Every event has its own page with the payload shape and a field reference. Browse them under Events in the sidebar, grouped by resource:
  • Checkout: checkout.completed, checkout.expired
  • Payments: collection.succeeded, collection.failed, collection.underpaid
  • Payment methods: payment_method.saved
  • Subscriptions: customer.subscription.created, customer.subscription.updated, customer.subscription.deleted
  • Invoices: invoice.created, invoice.paid, invoice.payment_failed
  • Withdrawals: payout.created, payout.paid, payout.failed
  • Refunds: refund.created, refund.paid, refund.failed
  • Disputes: dispute.created, dispute.updated
  • Conversions: conversion.completed, conversion.failed
  • Customers: customer.created, customer.updated
  • Connect: account.updated, capability.updated, transfer.created
To re-deliver a past event, see Replay events.