Webhooks
Get notified the moment a lodgement changes status, instead of polling. Register an HTTPS endpoint and we'll push you a signed event.
- General Overview
- Announcements
- Authentication
- Webhooks
- Endpoints Summary
- Expected Outcomes
- Onboarding Requirements
- Known Limitations
- Teams
Overview
Webhooks let you subscribe to status changes on your lodgements instead of polling GET /status endpoints. When a subscribed request transitions into a notifiable status, we send an HTTP POST with a small JSON event to your endpoint and retry on failure.
Webhook payloads are deliberately minimal: a nudge to fetch full detail, not a replacement for it. Treat a webhook as "something changed, go check GET /status", not as the source of truth itself.
Registering an Endpoint
Register endpoints from the API Keys page in your dashboard (each API key has its own Webhooks panel, under the Live or Test tab), or programmatically:
| Method | Path | Purpose |
|---|---|---|
| GET | /api/v2/webhooks | List endpoints for this key (secret omitted) |
| POST | /api/v2/webhooks | Create an endpoint |
| PATCH | /api/v2/webhooks/{id} | Update URL, event types, or enable/disable |
| DELETE | /api/v2/webhooks/{id} | Remove an endpoint |
| POST | /api/v2/webhooks/{id}/rotate-secret | Invalidate the current signing secret and issue a new one |
Swap /api/v2/ for /api/test/v2/ to manage test-environment endpoints. Endpoints are scoped to the API key (and therefore environment) they were created under, so a test key's webhooks only ever fire for test-key lodgements. Secret keys only; same rate limit and card requirement as other lodgement endpoints.
Create an endpoint:
The response includes a secret field (whsec_...). Store it immediately. It's shown once, on create and again on rotation, and is never returned by GET.
urlmust be HTTPS and resolve to a public IP: private, loopback, link-local, and carrier-grade NAT ranges are rejected, both when you register the endpoint and again on every deliveryeventTypesis a non-empty array; use["*"]to subscribe to everything- Up to 10 endpoints per API key
- Disabling an endpoint (
enabled: falseviaPATCH) stops deliveries immediately and resets its failure count when re-enabled
Events
A delivery fires when a request's status changes into one of the statuses below. Each transition is delivered independently. A request that moves from manual review to finished fires two separate events.
| Event | Interaction | Notifiable statuses |
|---|---|---|
| company_registration.status_changed | company-registration | finished, rejected, validation failed, manual review, error |
| abn_registration.status_changed | abnreg.lodge | finished, rejected, manual review, error |
| business_name_registration.status_changed | business-name-registration | finished, rejected, manual review, error |
| business_name_renewal.status_changed | business-name-renewal | finished, rejected, manual review, error |
| ppsr.alert.grantor_registration_changed | N/A - PPSR mailbox notification, not status-driven | Fires whenever PPSR notifies us of a new registration against an organisation you're watching via an active PPSR alert subscription |
ppsr.alert.grantor_registration_changed has a different payload shape to the others above - it has no requestId or interaction, and instead carries organisationNumberType, organisationNumber, and registrationSummary alongside the common event, timestamp, and deliveryId fields.
Payload
Every delivery is a POST with a JSON body and Content-Type: application/json:
| Field | Type | Notes |
|---|---|---|
| event | string | One of the event names above |
| requestId | integer | The request id (pair it with GET /status for full detail) |
| interaction | string | The interaction type, e.g. company-registration |
| status | string | The status this transition landed on |
| timestamp | string | ISO 8601, UTC |
| entityName / acn / abn | string, optional | Omitted entirely (not sent as null) when not applicable, e.g. no acn on business name events |
| deliveryId | string | Stable across retries of the same delivery, so use it to dedupe |
Verifying Signatures
Every delivery includes an X-Bapi-Signature header so you can confirm it actually came from us:
Recompute the HMAC-SHA256 over {timestamp}.{raw_request_body} using your endpoint's secret, and compare it to v1 with a constant-time comparison:
Use the raw, unparsed request body. Re-serialising JSON before hashing can change byte-for-byte formatting and break the comparison. If you rotate your secret, both the old and new secret should be tried until you've confirmed the new one is live everywhere you verify.
Responding
- Return any 2xx status code to acknowledge (the response body isn't inspected)
- Respond within 15 seconds; slower responses are treated as a failed attempt
- Redirects are not followed: a 3xx counts as a failed attempt, so point the URL at its final destination
- Do the actual processing asynchronously on your side and return quickly. Don't make us wait on your downstream work
Retries & Failure Handling
A non-2xx response, a timeout, or a connection error is a failed attempt and is retried with backoff, up to 6 attempts total:
| Attempt | Delay since previous attempt |
|---|---|
| 1 | immediate |
| 2 | 30 seconds |
| 3 | 5 minutes |
| 4 | 15 minutes |
| 5 | 1 hour |
| 6 | 6 hours |
If attempt 6 also fails, the delivery is marked permanently failed. Consecutive failures accumulate per endpoint (any 2xx resets the count to zero); at 20 consecutive failures the endpoint is automatically disabled and we email the account owner. Re-enable it from the dashboard or via PATCH once your endpoint is healthy again (this also resets the failure count).
Webhooks are a convenience layer, not a guarantee of instant delivery: retries can stretch a permanently-failed delivery out to roughly 24 hours after the first attempt. If your integration depends on knowing the current status right now, call GET /status directly rather than waiting on a webhook.