API AND WEBHOOKS
The ChatBridge24 REST API
Anything that can make an HTTPS request can send a WhatsApp message through a number you have connected, and anything that can receive one can be told what happened to it. Two mechanisms carry most of the design: a required idempotency key, and a webhook queue that holds rather than drops.
The API exists so that the answer to does it work with our system is yes without waiting for us to build a connector. It is the same sending machinery the inbox and the campaigns use, with the same meters and the same refusals.
Keys are scoped, and a refusal says nothing useful to a stranger
One shape of refusal for every way a key can be wrong.
A key is created in the app, shown once, and stored as a hash — so it cannot be read back out of our database, by us or by anybody else. Each key carries scopes, so a key that only needs to read messages cannot send one.
Every failure to authenticate answers the same way: missing, malformed, unknown, revoked and expired are one response, not five. A refusal that distinguishes unknown from revoked tells a stranger that a key once existed on that account, which tells them a named business uses this product. The key prefix shown in the app is for your own eyes and is never a lookup path.
Every send needs an idempotency key, and that is not optional
Because the alternative is a stranger receiving the same message twice.
Charging for a WhatsApp message is not an operation that can be repeated safely, and a public API is retried by HTTP clients, job queues and load balancers that have no idea a retry costs money. So the send endpoints require an idempotency key and refuse a request without one.
The key claims the send in a single database statement before any money moves. Three answers follow, and they are deliberately different from each other:
- A replay of a finished request returns the stored outcome, so a client that lost the response gets the real answer rather than a second message.
- A replay while the first is still in flight is refused as in-progress rather than replayed. A half-finished outcome is not an outcome.
- A refusal raised before the transport was called releases the key, so a timeout on our side does not lock that key for ever. A refusal raised after the charge keeps it, because the money moved.
And the outcome of a key can be looked up afterwards, including the identifier of the charge — which is how a caller that crashed mid-request finds out what it did without sending anything.
Outgoing webhooks
Signed, retried, and switched off in a way that keeps the events.
You register an endpoint
An HTTPS URL on a host that resolves to a public address. A private address, a loopback address, a cloud metadata address or a bare IPv6 literal is refused — and the check happens at connection time rather than at registration, because a name that resolved publicly once can resolve privately a second later.
We sign every delivery
The signature is over the raw body. Verify it before parsing: once JSON has been parsed and re-serialised the bytes have changed and no signature can match again.
A failure is retried for about eleven hours
With a widening gap, in batches, across your endpoints — so one slow endpoint does not starve the others.
A dead endpoint is switched off, and its events are HELD
After a long run of consecutive failures spanning hours, the endpoint is disabled. Its events are kept, not discarded: a successful test from the app re-enables the endpoint and releases what it missed. An endpoint we disabled is the one case where a gap is recoverable.
An event type you can subscribe to is an event type something emits
A promise the product cannot keep is worse than a missing feature.
This is worth stating because it was once untrue here. The subscription screen offered five event types and only one of them had anything behind it — and the test button sent that one, so a subscription that could never fire came back green. The other four were removed rather than left.
So the rule is now mechanical: a type appears in the list only if something in the product emits it, and the test delivery carries its own type so it can never stand in for a real one. The same rule governs the scopes a key can hold: a scope is listed only if a route requires it.
What the API does not do
Four absences, so you find them here rather than at integration.
It does not get a template approved
Approval is the transport’s decision and happens against your own account. The API sends templates that are already approved.
It does not bypass the window
Outside the free-text window a send must be an approved template, exactly as it must in the inbox. The API is not a different set of rules.
It does not replay a webhook you lost by accident
Events are held for an endpoint we disabled. An endpoint that answered 200 and then dropped the payload on the floor has been delivered to.
It has no sandbox number
You test against your own connected number. A shared test number that reaches a handful of pre-registered recipients teaches you very little about your own account’s limits.
Questions
Do I need a plan to use the API?
How many endpoints can one account have?
What happens if I reuse an idempotency key for a different message?
Is the wallet balance in the API?
Where it sits
Any CRM, over one API
The same API, framed as the answer to a CRM we have not built a connector for.
Documentation
Connecting a number, getting a template approved, and the setup the API sits on.
How we protect your data
Where the keys are stored and what a key can reach.