Create a webhook subscription
BuildingIn development for the v1 API. The contract is set; the route is not served yet.
Registers a URL to receive the events listed, signed with a secret of its own. The answer carries secret once: store it, it is never shown again. Retrying with the same Idempotency-Key and body returns the same subscription, secret included. A company can hold up to 10 subscriptions. See the Webhooks guide for signature verification, idempotency and retries.
Needs the webhooks:write scope.
oauth2 webhooks:writeAuthorizationBearer <token>OAuth 2.0 client credentials. Exchange your client_id and client_secret at /oauth/token of the environment for a bearer token valid for one hour. The playground of every operation asks for your own client and keeps it in your browser tab only.
Scope: webhooks:write
Idempotency-Key*stringUnique per operation. Retrying with the same key returns the original result for 24 hours.
length <= 255application/json- body
url*stringAbsolute https URL heru POSTs to: a public host, port 443 or 1024 and above, no credentials and no fragment. heru does not follow redirects.
urilength <= 2048event_types*array<string>Types from the WebhookEvent.type enum, or * for every current and future type. If * is present only * is kept.
1 <= items <= 20merchant_ids?array<>Only events of these merchants. Empty or absent means every merchant of the company.
items <= 100description?stringlength <= 500headers?array<>Extra headers sent on every delivery, such as a token your gateway expects.
items <= 5The subscription is active. Store secret: it signs every delivery and is never shown again.
application/json- response
id*string^whk_[0-7][0-9a-hjkmnp-tv-z]{25}$url*stringuridescription*stringevent_types*array<string>merchant_ids*array<>status*stringactive delivers; paused you set it and deliveries wait; disabled heru set it (see disabled_reason) and deliveries wait until you send status: active.
"active""paused""disabled"disabled_reason*|nulldisabled_at*|nullheaders*array<>secret?stringOnly in the answer of POST /webhooks and POST /webhooks/{id}/secret. Store it: it is never shown again.
^whsec_[A-Za-z0-9_-]{43}$secret_last4*stringsecret_rotating_until*|nullconsecutive_failures*integerFailed deliveries in a row. A 2xx resets it to zero.
failing_since*|nullenvironment*string"sandbox""production"created_at*stringdate-timeupdated_at*stringdate-timedeliveries*Where the delivery log of this subscription is.
curl -X POST 'https://api.dev.b2b.heru.app/v1/webhooks' \ -H 'Idempotency-Key: try-it-0001' \ -H 'Content-Type: application/json' \ -d '{ "url": "https://partner.example.com/heru/webhooks", "event_types": [ "invoice.stamped", "charge.invoiced" ], "merchant_ids": [ "mer_01m4ra8wsyfh3s0tr4nrq3y96j" ], "description": "POS production", "headers": [ { "name": "X-Partner-Token", "value": "s3cr3t-token-abcd" } ]}'{ "id": "whk_01m53g5zw414zt6vg08fqbacgm", "url": "https://partner.example.com/heru/webhooks", "description": "POS production", "event_types": [ "invoice.stamped", "charge.invoiced" ], "merchant_ids": [ "mer_01m4ra8wsyfh3s0tr4nrq3y96j" ], "status": "active", "disabled_reason": null, "disabled_at": null, "headers": [ { "name": "X-Partner-Token", "value": "****abcd" } ], "secret": "whsec_MfKdm7bK1VQmYzmmtzMTeUwRUOz9ozNtg_xq6WfL2Do", "secret_last4": "L2Do", "secret_rotating_until": null, "consecutive_failures": 0, "failing_since": null, "environment": "production", "created_at": "2026-10-25T12:00:00.000Z", "updated_at": "2026-10-25T12:00:00.000Z", "deliveries": { "path": "/v1/webhooks/whk_01m53g5zw414zt6vg08fqbacgm/deliveries" }}Usage reportGET
What you used in the period, per service. It matches your invoice from heru. Needs the `reports:read` scope.
Rotate the signing secretPOST
Mints a new secret and answers it once. The previous secret keeps signing for `expires_previous_in` (24 hours by default, 7 days at most), so every delivery carries two `v1` signatures meanwhile and you can switch without missing an event; `0s` retires it at once. Never more than two secrets sign: a second rotation inside the overlap retires the oldest. At most 5 rotations per hour per subscription. No `Idempotency-Key`: every call rotates. Needs the `webhooks:write` scope.