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.
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.

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.