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 with test (EVTE sandbox). Add a second endpoint for live when you go to production.
- Events: subscribe to submission.accepted, submission.rejected, and submission.error at a minimum. submission.pending / submission.sent are 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