Receive events reliably
The rules every webhook endpoint must follow, covering authenticity, organization checks, duplicates, fast acknowledgment and fetching data from the API.
This page describes how to build an endpoint that receives BreezeAuth webhooks safely and without losing or double-processing events. It is written for developers, but the summary below is useful for anyone responsible for an integration.
Summary
For every request it receives, your endpoint must:
- Verify the signature. Reject the request if it is missing or invalid.
- Check the organization. Reject the message if
organizationsUuidis not the organization you expect. - Ignore duplicates. If you have already received this
webhook-id, reply2xxand stop. - Store the message and reply
2xximmediately, well within 15 seconds. - Process the message in the background: fetch the current data from the API and update your system.
POST /webhooks/breezeauth
├─ signature invalid? → 401, stop
├─ organizationsUuid unexpected? → 403, stop
├─ webhook-id already seen? → 204, stop
├─ save message + webhook-id (one transaction)
└─ 204 → a background worker processes the saved message
Verify the signature
The signature is the only proof that a message was sent by BreezeAuth. Check it before reading anything else from
the message, and reject the request with a 4xx status if it fails. All the details, with code examples, are in
Verify signatures.
Check the organization
A valid signature proves that the message comes from BreezeAuth, but BreezeAuth serves many organizations, and every one of them signs with the same keys. Your endpoint must also make sure the message is about your organization.
Compare the organizationsUuid field of the body with the identifier of the organization your system expects, and
reject the message if they differ. This protects you, for example, if your endpoint URL is registered by mistake, or
on purpose, as a webhook in another organization.
If one endpoint serves several organizations, for example in a multi-tenant product, keep the list of organization identifiers it accepts and look each message up in that list.
Note: your organization’s identifier is the organizationsUuid value. It is shown under the organization’s name
on the Manage Organization page, with a button to copy it.
Ignore duplicates
BreezeAuth delivers each event at least once. In some situations the same message can arrive more than once, for example:
- your endpoint processed the message, but its response took longer than 15 seconds or was lost on the network, so BreezeAuth considered the attempt failed and retried;
- a temporary failure inside BreezeAuth’s infrastructure caused a message that had already been delivered to be sent again;
- two webhooks in your organization point to the same URL and subscribe to the same event.
Duplicate detection on your side is therefore mandatory. Each message has a unique identifier, sent in the
webhook-id header and repeated as uuid in the body. It is the same across every delivery of the same event.
A simple and robust approach is a table with a unique constraint on the identifier:
CREATE TABLE breezeauth_webhook_messages (
webhook_id VARCHAR(64) PRIMARY KEY,
received_at TIMESTAMP NOT NULL,
payload TEXT NOT NULL,
processed_at TIMESTAMP NULL
);
When a message arrives, insert its identifier and payload in a single transaction. If the insert fails because the
identifier already exists, the message is a duplicate: reply 2xx without doing anything else. Keep the identifiers
for at least a few days; keeping them longer costs little and makes your audit trail more complete.
Tip: reply 2xx to duplicates, not an error. An error would make BreezeAuth retry a message you have already
received.
Acknowledge quickly, process later
BreezeAuth waits at most 15 seconds for a response. Your endpoint should reply as soon as the message is safely stored, and leave the actual work (calling the API, updating databases, sending e-mails) to a background process such as a job queue, a worker or a scheduled task.
- Reply with any
2xxstatus, such as200 OKor204 No Content. The response body is ignored. - Don’t call the BreezeAuth API or other slow services before replying.
- If your endpoint can’t store the message (for example, your database is down), reply with a
5xxstatus so BreezeAuth retries it.
Doing heavy work synchronously is the most common cause of timeouts, and therefore of retries and duplicates.
Fetch the details from the API
Messages only carry identifiers. When your background process needs
more information, it reads the current state from the BreezeAuth API, at https://api.breezeauth.com.
-
In the admin console, create a service user under Users › Service Users and issue a credential for it. You get a client ID and a client secret. Give the service user only the access your integration needs, for example the Auditor role for read-only access. See Service users.
-
Exchange the credential for an access token:
curl -X POST https://api.breezeauth.com/oauth/token \ -H 'Content-Type: application/json' \ -d '{"grant_type": "client_credentials", "client_id": "<client ID>", "client_secret": "<client secret>"}'The response contains an
access_tokenand its lifetime in seconds,expires_in. Reuse the token until it expires, then request a new one. See Access tokens for details and code examples. -
Call the API with the token in the
Authorizationheader:curl https://api.breezeauth.com/organizations/<organizationsUuid>/memberships/<membershipsUuid> \ -H 'Authorization: Bearer <access token>'
Where to read each resource:
| Events | API request |
|---|---|
membership.* |
GET /organizations/{organizationsUuid}/memberships/{membershipsUuid} |
group.* |
GET /organizations/{organizationsUuid}/groups/{groupsUuid} |
group_assignment.* |
GET /organizations/{organizationsUuid}/groups/{groupsUuid}/assignments/{groupAssignmentsUuid} |
application.* |
GET /organizations/{organizationsUuid}/applications/{applicationsUuid} |
application_assignment.* |
GET /organizations/{organizationsUuid}/applications/{applicationsUuid}/assignments/{applicationAssignmentsUuid} |
domain.* |
GET /organizations/{organizationsUuid}/domains/{domainsUuid} |
Things to keep in mind:
- Deleted resources. After a
*.deletedevent, the resource is usually no longer available and the API responds with404 Not Found. Treat that as confirmation of the deletion. - Order is not guaranteed. Messages can arrive in a different order than the events happened, for example a
membership.updatedbefore the matchingmembership.created. Because you always read the current state from the API, the order doesn’t matter: process each message as “something changed on this resource, let me look at it now”. - Several messages, one read. When many events about the same resource arrive close together, your background process can read the resource once and handle them all.
Reconcile periodically
Webhooks keep your system up to date in near real time, but they are not a full synchronization mechanism. If your endpoint is unavailable for longer than the retry window, or if a webhook is disabled for a while, some events will not be delivered. BreezeAuth does not currently offer a way to resend them.
For integrations where missing a change matters, such as removing access of people who left the company, add a periodic reconciliation: for example, once a day, list the relevant resources from the API and fix any difference with your system. Webhooks then handle the day-to-day changes, and reconciliation catches whatever slipped through.
Checklist
- The endpoint uses
https://and the URL registered in BreezeAuth is the final address (no redirects). - Optionally, the firewall only accepts requests from BreezeAuth’s IP addresses.
- The signature is verified on the raw body before anything else.
- Messages for an unexpected
organizationsUuidare rejected. -
webhook-idis stored with a unique constraint, and duplicates are answered with2xx. - The endpoint replies within a few seconds and processes messages in the background.
- Unknown event types are acknowledged with
2xxand ignored. - Details are fetched from the API, and
404after a deletion is handled. - A periodic reconciliation exists for critical data.
- Someone checks the event history when deliveries fail.
Last updated: October 13, 2026