What are webhooks?
Webhooks are HTTP POST requests that Beeswax STP sends to your server whenever a submission changes state. Instead of polling our API to check if an ATO submission has been accepted, we push the news to you the instant it happens.
Webhooks are available on the API plan ($49/mo).
Setting up an endpoint
Open Settings → Webhooks from the sidebar. Click Add endpoint and fill in:
| Field | Notes |
|---|---|
| URL | The endpoint on your server that will receive POST requests. Must be HTTPS in production. |
| Environment | test for EVTE sandbox submissions, live for production ATO submissions. |
| Description | Optional label for your own reference. |
| Events | Checkboxes for the event types you want. Leave all unchecked to receive every event. |
When you save, Beeswax STP generates a signing secret that's shown exactly once. Copy it immediately to your server's config — we only keep a hash, so there's no way to retrieve it later. If you lose it, click Rotate secret on the endpoint detail page to get a new one.
Worked example. A Ruby payroll platform integrating Beeswax STP for ATO compliance would register:
- URL:https://app.yourpayrollapp.com/webhook/beeswax_stp
- Environment: start withtest(EVTE sandbox). Add a second endpoint forlivewhen you go to production.
- Events: subscribe tosubmission.accepted,submission.rejected, andsubmission.errorat a minimum.submission.pending/submission.sentare useful for progress UI but not required.
Event types
These are the events Beeswax STP currently emits:
| Event | When it fires |
|---|---|
submission.pending |
A submission has been queued internally for lodgement |
submission.sent |
Handed off to the ATO SBR gateway, awaiting response |
submission.accepted |
ATO confirmed acceptance — store data.submission.message_id as the receipt |
submission.rejected |
ATO validation failed — details in data.submission.error_messages |
submission.error |
Something went wrong on our side (signing, network, transport) |
What you'll receive
Every delivery is a POST with these transport headers:
Content-Type: application/json
User-Agent: Beeswax-STP-Webhooks/1.0
Beeswax-Signature: t=<unix-timestamp>,v1=<hmac-sha256 hex>
Beeswax-Event-Type: submission.accepted
Body:
{
"id": "evt_abc123...",
"type": "submission.accepted",
"created": "2026-04-21T02:13:00Z",
"data": {
"submission": {
"id": 501,
"status": "accepted",
"message_id": "ATO-MSG-12345",
"error_messages": null,
"sent_at": "2026-04-21T02:12:44Z",
"response_received_at": "2026-04-21T02:12:58Z"
},
"pay_event": { "id": 300, "employer_id": 12, "pay_period_start": "2026-04-01", "...": "..." },
"employer": { "id": 12, "name": "Acme Pty Ltd", "abn": "51824753556" }
}
}
Response your endpoint must return
Any 2xx status is treated as a successful delivery — 200 OK is conventional. Respond within 10 seconds or we'll mark the delivery timed out and schedule a retry. For that reason, your handler should do the minimum (verify signature, enqueue a background job) and respond immediately — don't update your database inline.
Verifying signatures
Every webhook is signed with HMAC-SHA256 over the string "{timestamp}.{body}", using the signing secret as the key. Always verify the signature before acting on a webhook — otherwise an attacker who knows your URL could POST arbitrary data.
Minimum Ruby verification:
def verify(raw_body, header, secret)
parts = header.split(",").to_h { |p| p.split("=", 2) }
t, v1 = parts["t"], parts["v1"]
return false if t.nil? || v1.nil?
# Replay protection: reject deliveries older than 5 minutes.
return false if (Time.current.to_i - t.to_i).abs > 300
expected = OpenSSL::HMAC.hexdigest("SHA256", secret, "#{t}.#{raw_body}")
ActiveSupport::SecurityUtils.secure_compare(expected, v1)
end
For complete verification code in Node.js and Python, see Webhooks developer guide.
Delivery reliability
If your server returns a non-2xx status or takes longer than 10 seconds to respond, we retry with exponential backoff:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 25 minutes |
| 5 | 2 hours |
| 6 | 10 hours |
| 7 | 1 day |
| 8 | 2 days |
After 8 failed attempts the delivery is abandoned. After 10 consecutive failures across different events, the endpoint is auto-paused — no more events go out until you reactivate it on the endpoint detail page.
Monitoring deliveries
Click any endpoint to see its delivery history: which events have been sent, their current status, HTTP response codes, and how many attempts were needed. This is the first place to look if a webhook isn't arriving.
Replaying missed events
If your server was down during a delivery, or you discover a bug in your handler that silently dropped events, you can replay them from the event archive:
GET /api/v1/events?endpoint_id=<id>— cursor-paginated, 90-day retention.POST /api/v1/events/:id/resend— re-delivers a specific event to its endpoint.
Don't rely on the retry schedule alone — it stops after 8 attempts.
Rotating the signing secret
If you suspect your signing secret has been compromised, click Rotate secret on the endpoint detail page. A new secret is generated and shown once. The old secret is invalidated instantly — make sure your server is ready to accept the new one before clicking rotate, or you'll reject legitimate deliveries until it's updated.
Further reading
- Webhooks developer guide — full technical reference with code samples in Node.js and Python
- API overview
- Errors