curl --request POST \
--url https://api.bachs.io/v1/webhooks/endpoints \
--header 'Content-Type: application/json' \
--data '
{
"name": "Production events",
"url": "https://api.example.com/webhooks/bachs",
"event_types": [
"checkout.completed",
"collection.succeeded",
"payout.paid",
"refund.paid"
],
"event_source": "account"
}
'const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
name: 'Production events',
url: 'https://api.example.com/webhooks/bachs',
event_types: ['checkout.completed', 'collection.succeeded', 'payout.paid', 'refund.paid'],
event_source: 'account'
})
};
fetch('https://api.bachs.io/v1/webhooks/endpoints', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.bachs.io/v1/webhooks/endpoints"
payload = {
"name": "Production events",
"url": "https://api.example.com/webhooks/bachs",
"event_types": ["checkout.completed", "collection.succeeded", "payout.paid", "refund.paid"],
"event_source": "account"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.bachs.io/v1/webhooks/endpoints"
payload := strings.NewReader("{\n \"name\": \"Production events\",\n \"url\": \"https://api.example.com/webhooks/bachs\",\n \"event_types\": [\n \"checkout.completed\",\n \"collection.succeeded\",\n \"payout.paid\",\n \"refund.paid\"\n ],\n \"event_source\": \"account\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}{
"endpoint_id": "whe_a1e823c073ab743ce5969ceef2db4d42",
"name": "Production events",
"url": "https://api.example.com/webhooks/bachs",
"enabled": true,
"disabled_at": null,
"disabled_reason": null,
"event_types": [
"checkout.completed",
"collection.succeeded",
"payout.paid",
"refund.paid"
],
"event_source": "account",
"created_at": "2026-03-09T10:00:00.000Z",
"updated_at": "2026-03-09T10:00:00.000Z",
"signing_secret": "whsec_62da9edb5b97b120f7d55e1e190118b0ec08fcace294dfaf211ec370b6d21f34"
}{
"detail": "Invalid request parameters",
"error_code": "VALIDATION_ERROR",
"errors": [
{
"field": "amount",
"message": "Amount must be a positive decimal string",
"type": "value_error"
}
]
}{
"detail": "Invalid API key",
"error_code": "UNAUTHORIZED"
}{
"detail": "API key does not have permission for this operation",
"error_code": "FORBIDDEN"
}{
"detail": "Validation failed for one or more fields",
"error_code": "VALIDATION_ERROR",
"doc_url": "https://docs.bachs.io/api-reference/error-reference#general",
"errors": [
{
"field": "name",
"message": "This field is required",
"type": "missing"
}
]
}{
"detail": "Rate limit exceeded. Please retry after a few seconds.",
"error_code": "TOO_MANY_REQUESTS"
}Create a webhook endpoint
Register a URL to receive webhook events, and choose which events to subscribe to. The signing secret is returned once in the response. Requires the webhooks:write scope.
curl --request POST \
--url https://api.bachs.io/v1/webhooks/endpoints \
--header 'Content-Type: application/json' \
--data '
{
"name": "Production events",
"url": "https://api.example.com/webhooks/bachs",
"event_types": [
"checkout.completed",
"collection.succeeded",
"payout.paid",
"refund.paid"
],
"event_source": "account"
}
'const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
name: 'Production events',
url: 'https://api.example.com/webhooks/bachs',
event_types: ['checkout.completed', 'collection.succeeded', 'payout.paid', 'refund.paid'],
event_source: 'account'
})
};
fetch('https://api.bachs.io/v1/webhooks/endpoints', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.bachs.io/v1/webhooks/endpoints"
payload = {
"name": "Production events",
"url": "https://api.example.com/webhooks/bachs",
"event_types": ["checkout.completed", "collection.succeeded", "payout.paid", "refund.paid"],
"event_source": "account"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.bachs.io/v1/webhooks/endpoints"
payload := strings.NewReader("{\n \"name\": \"Production events\",\n \"url\": \"https://api.example.com/webhooks/bachs\",\n \"event_types\": [\n \"checkout.completed\",\n \"collection.succeeded\",\n \"payout.paid\",\n \"refund.paid\"\n ],\n \"event_source\": \"account\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}{
"endpoint_id": "whe_a1e823c073ab743ce5969ceef2db4d42",
"name": "Production events",
"url": "https://api.example.com/webhooks/bachs",
"enabled": true,
"disabled_at": null,
"disabled_reason": null,
"event_types": [
"checkout.completed",
"collection.succeeded",
"payout.paid",
"refund.paid"
],
"event_source": "account",
"created_at": "2026-03-09T10:00:00.000Z",
"updated_at": "2026-03-09T10:00:00.000Z",
"signing_secret": "whsec_62da9edb5b97b120f7d55e1e190118b0ec08fcace294dfaf211ec370b6d21f34"
}{
"detail": "Invalid request parameters",
"error_code": "VALIDATION_ERROR",
"errors": [
{
"field": "amount",
"message": "Amount must be a positive decimal string",
"type": "value_error"
}
]
}{
"detail": "Invalid API key",
"error_code": "UNAUTHORIZED"
}{
"detail": "API key does not have permission for this operation",
"error_code": "FORBIDDEN"
}{
"detail": "Validation failed for one or more fields",
"error_code": "VALIDATION_ERROR",
"doc_url": "https://docs.bachs.io/api-reference/error-reference#general",
"errors": [
{
"field": "name",
"message": "This field is required",
"type": "missing"
}
]
}{
"detail": "Rate limit exceeded. Please retry after a few seconds.",
"error_code": "TOO_MANY_REQUESTS"
}Body
Parameters for creating a webhook endpoint.
A label you choose to tell this endpoint apart from your others in the dashboard and in list responses. It is never sent to the endpoint.
"Production events"
The HTTPS URL we POST each subscribed event to. It must be reachable from the public internet and should return a 2xx quickly, since a slow or non-2xx response is recorded as a failed attempt and retried.
"https://api.example.com/webhooks/bachs"
The event types this endpoint receives. Send at least one; anything not listed is never delivered here, so add a type before you rely on it. Values are matched case-insensitively and stored lowercase.
1collection.succeeded, collection.failed, collection.underpaid, checkout.completed, checkout.expired, payout.created, payout.paid, payout.failed, refund.created, refund.paid, refund.failed, conversion.completed, conversion.failed, customer.created, customer.updated, dispute.created, dispute.updated, customer.subscription.created, customer.subscription.updated, customer.subscription.deleted, invoice.created, invoice.paid, invoice.payment_failed ["collection.succeeded", "collection.failed"]
Which account's events reach this endpoint. account: only events that happened on your own account. connect: only events that happened on accounts you own. all: both. Defaults to account when you omit it.
account, connect, all "account"
Response
The created endpoint, including its signing secret.
The created endpoint, plus its signing secret. The secret is returned only once, on creation. Store it securely.
Use this identifier to fetch, update, delete, or rotate the secret on this endpoint, and to scope event listings to it.
"whe_a1e823c073ab743ce5969ceef2db4d42"
The label you gave the endpoint. It is never sent to the endpoint itself.
"Production events"
The HTTPS URL we POST each subscribed event to.
"https://api.example.com/webhooks/bachs"
When true, matching events are delivered here. A new endpoint is enabled straight away, so have your receiver deployed before you create it.
true
The event types this endpoint now receives, stored lowercase. Anything not listed is never delivered here.
collection.succeeded, collection.failed, collection.underpaid, checkout.completed, checkout.expired, payout.created, payout.paid, payout.failed, refund.created, refund.paid, refund.failed, conversion.completed, conversion.failed, customer.created, customer.updated, dispute.created, dispute.updated, customer.subscription.created, customer.subscription.updated, customer.subscription.deleted, invoice.created, invoice.paid, invoice.payment_failed ["collection.succeeded", "collection.failed"]
When the endpoint was created, as an ISO 8601 timestamp in UTC.
"2026-03-09T10:00:00.000Z"
When the endpoint was last changed, as an ISO 8601 timestamp in UTC. Rotating the signing secret updates this.
"2026-03-09T10:00:00.000Z"
Use this to verify the X-Bachs-Signature header on every delivery to this endpoint and reject anything that does not match. It is shown in full only in this response, so store it in your secret manager now; afterwards you can read it back with GET /v1/webhooks/endpoints/{endpoint_id}/secret (scope webhooks:read) or replace it by rotating.
"whsec_62da9edb5b97b120f7d55e1e190118b0ec08fcace294dfaf211ec370b6d21f34"
When we turned this endpoint off because its deliveries kept failing, as an ISO 8601 timestamp in UTC. null while the endpoint is enabled, and when you turned it off yourself. See When an endpoint keeps failing.
"2026-03-12T14:05:00.000Z"
Why we turned this endpoint off. consecutive_failures: every delivery to it failed for at least 72 hours, with no success in between, and we emailed a warning at least 48 hours earlier. null while the endpoint is enabled, and when you turned it off yourself.
consecutive_failures, null "consecutive_failures"
Which account's events reach this endpoint. account: only your own account's events. connect: only the events of accounts you own. all: both.
account, connect, all "account"

