Founding offer — 25% off applied automatically at checkout. Ends 1 November 2026 or after our first 30 orders. See pricing →
Skip to document content
Guide

Webhooks

10 min read 8 views
Version
1.0
Last Version Update
October 4, 2026
Effective From
October 4, 2026
Review Cycle
Annual
Next Review Due
October 2027

Webhooks

🔒 Pro feature — requires the Pro plugin.

Webhooks let Attestation Core tell another system when something important happens, without anyone copying data across by hand. When a staff member’s evidence is validated, a plan is approved or returned, a credential is verified or an expiry alert goes out, the plugin sends a small JSON message to each web address (an “endpoint”) you’ve registered for that event.

This is for administrators and whoever looks after your organisation’s integrations: an HR system, a workflow tool such as Zapier, Make or Power Automate, or an in-house dashboard. Staff members don’t see webhooks.

Where to find it

Attestation Core → Settings → Webhooks (Pro). The tab and every action on it (save, delete, retry) need the “manage settings” permission, which administrators have. Free sites don’t have this tab.

The tab has three parts: Add webhook endpoint, Configured endpoints and Delivery log.

Adding an endpoint

  1. In your receiving system, create a URL that accepts JSON POST requests. In Zapier this is a “Webhooks by Zapier → Catch Hook” trigger, in Make a “Custom webhook”, and in Power Automate a “When an HTTP request is received” trigger.
  2. Go to Settings → Webhooks (Pro).
  3. Endpoint URL: paste the address. It must be a valid web address and must use HTTPS. Plain http:// addresses are refused with “Webhook URL must use HTTPS.”, and malformed ones with “Webhook URL must be a valid HTTP or HTTPS URL.”
  4. Events: tick at least one event (see the list below). If you tick none, the endpoint isn’t saved.
  5. HMAC secret (optional): enter a shared secret if your receiver will check signatures. It’s stored encrypted and never shown again.
  6. Active (“Enable immediate delivery”): ticked by default. Untick it to save the endpoint in a paused state.
  7. Click Add webhook endpoint. You’ll see “Webhook endpoint saved.”

Configured endpoints lists each endpoint with its URL, events, status (Active or Paused) and created date, plus a Delete button. Paused endpoints receive nothing, and nothing is queued for them. The tab has no edit form. To change an endpoint’s URL, events or secret, delete it and add it again. Each endpoint has a number (for example #3) that appears in the delivery log.

You can register as many endpoints as you like. Each event is sent to every active endpoint subscribed to it.

Events

Event (as shown on the tab) Event name in the message When it fires Payload fields
Evidence validated evidence_validated An admin validates a staff member’s evidence evidence_id, trainer_id, unit_ids (list of linked unit IDs), type, title, validated_at
Annual plan approved plan_approved An admin approves an annual plan plan_id, new_status, old_status, reviewer_id, changed_at
Annual plan rejected plan_rejected An admin returns a plan for revision plan_id, new_status (returned), old_status, reviewer_id, changed_at
Credential verified credential_verified An admin marks a credential as verified credential_id, trainer_id, verified_at
Document expiry alert sent expiry_alert_sent A document-expiry email has been successfully sent document_id, trainer_id, tier (90, 60, 30, 7 or expired), sent_at

Notes:

  • trainer_id is the internal ID of the staff member’s compliance profile. It isn’t their name or email. Messages carry IDs and titles, not personal contact details, so look people up in Attestation Core if you need more.
  • Dates are in UTC, formatted as YYYY-MM-DD HH:MM:SS.
  • old_status holds the plan’s status as stored after the change, so it usually matches new_status. Don’t rely on it to work out the previous state.
  • Expiry alert events depend on email notifications. On Free-tier cadence only the 30-day and expired tiers occur. Pro adds 90, 60 and 7. Annual-review emails don’t trigger this event. Because document reminders can repeat daily (see Automated email notifications), you may receive several expiry_alert_sent events for the same document.
  • The plan events need the Pro annual plan feature. See Annual plans.

What each message looks like

Each delivery is an HTTPS POST with a JSON body. The values below are illustrative, and type is the slug of the evidence category the item was submitted under:

{
  "event": "evidence_validated",
  "timestamp": 1790000000,
  "webhook_id": 3,
  "payload": {
    "evidence_id": 412,
    "trainer_id": 27,
    "unit_ids": [15, 18],
    "type": "industry_engagement",
    "title": "Placement at Riverside Aged Care",
    "validated_at": "2026-10-05 03:14:07"
  }
}

Headers sent with every delivery:

Header Value
Content-Type application/json; charset=utf-8
X-Attestation-Core-Event The event name, e.g. plan_approved
X-Attestation-Core-Time Unix timestamp (seconds) when the message was signed
X-Attestation-Core-Delivery A unique ID for this delivery attempt. Use it to spot replays and duplicates.
X-Attestation-Core-Signature Only when the endpoint has an HMAC secret: a hex-encoded HMAC-SHA256 of {timestamp}.{raw JSON body} using your secret

Checking the signature (for your developer): take the X-Attestation-Core-Time value, a full stop, and the exact raw request body. Compute HMAC-SHA256 with the shared secret and compare it to X-Attestation-Core-Signature using a constant-time comparison. Also reject messages where the timestamp is more than 5 minutes from your server’s clock. The timeout for each request is 15 seconds, so reply quickly (any 2xx status) and do slow work afterwards.

Delivery, retries and the delivery log

Queued delivery (the drain task)

Webhooks aren’t sent while an admin is waiting. When an event happens, the plugin puts a message in an outbound queue for each subscribed endpoint, and a background task picks them up:

  • The webhook drain task is scheduled to run every minute through WP-Cron. Like all WP-Cron tasks it only runs when the site has traffic, so on a quiet site deliveries can be a few minutes late. A real server cron job makes them prompt.
  • Each run takes up to 20 waiting messages. A lock stops two runs from processing the same messages. When the queue is empty the task does nothing.
  • The URL is checked again (valid, HTTPS) when the message is queued. Endpoints deleted in the meantime have their queued messages discarded.

Retry policy

A delivery succeeds when the endpoint replies with any 2xx status. Anything else, including a timeout or connection error, counts as a failure:

Attempt If it fails, next try after
1 about 2 minutes
2 about 4 minutes
3 about 8 minutes
4 about 16 minutes
5 No further automatic retry. The message is set aside as “dead-lettered”.

So the plugin keeps trying for roughly half an hour, plus any WP-Cron delay, before it gives up. Dead-lettered messages aren’t retried automatically, and the tab has no separate dead-letter view. To resend one, use Retry on its row in the delivery log (below).

The delivery log

The Delivery log at the bottom of the tab shows 25 entries per page, newest first, with Previous/Next links. Columns:

  • Time (UTC): when the delivery was recorded.
  • Event: the event’s friendly name.
  • Endpoint: the endpoint number (e.g. #3).
  • HTTP: the status code your endpoint returned. 0 means no response at all (timeout, DNS or connection error).
  • Response: the first 280 characters of what your endpoint replied, or the error message.
  • Actions → Retry: resends that same payload to that endpoint straight away (not through the queue) and adds a new row to the log. You’ll see “Webhook delivery retried.”

What’s logged: one row for each successful delivery, and one row for a message’s final failure (after the fifth attempt). Earlier failed attempts aren’t logged individually. Each row stores the event, the endpoint, the payload that was sent, the HTTP status, your endpoint’s full response body and the time. Where the payload includes a staff member’s ID, the row is linked to them so it can be handled in personal-data requests. See Privacy, security and data.

Retention (the retention task)

A separate webhook log retention task runs once a day and deletes delivery-log entries older than 90 days. The settings screens don’t let you change this period.

Using webhooks with Slack, Microsoft Teams and Zapier

Attestation Core sends its own JSON format (shown above). It doesn’t produce Slack- or Teams-formatted messages itself:

  • Slack and Microsoft Teams: their “incoming webhook” URLs expect a message in their own format (for example a text field). If you paste one straight in as an endpoint, they’ll usually reject the delivery or post nothing, and the log will show a 4xx status. Instead, send the webhook to Zapier, Make or Power Automate, then add a step that posts a message to your Slack or Teams channel. For example: “Evidence validated: {payload.title}”.
  • Zapier: create a Zap with Webhooks by Zapier → Catch Hook, copy the hook URL into Endpoint URL, tick your events and save. Trigger a real event (for example validate a test evidence item) so Zapier can learn the fields, then build your actions. A Catch Hook can’t check signatures, so leave the HMAC secret blank or check it in a code step.
  • In-house systems: set an HMAC secret and check the signature and timestamp as described above.

Worked example

A registered training organisation wants its HR system to know when trainer evidence is validated, and wants a Teams message when a trainer’s document expires.

  1. Their developer creates an HTTPS endpoint in the HR system and adds it with the Evidence validated and Credential verified events and an HMAC secret. The HR system checks each signature and stores trainer_id, unit_ids and validated_at against its own staff record.
  2. The compliance manager creates a Power Automate flow with “When an HTTP request is received”, then “Post message in a chat or channel”. The flow’s URL goes in as a second endpoint with only Document expiry alert sent ticked. The flow posts a message only when payload.tier is expired.
  3. A week later a delivery to the HR system shows HTTP 503 in the log after an outage. Once the HR system is back, the manager clicks Retry on that row and a new row shows 200.

Troubleshooting

  • “Webhook URL must use HTTPS.” Use the https:// form of the address. Plain HTTP isn’t accepted.
  • Saving does nothing / no endpoint appears. At least one event must be ticked.
  • Nothing in the delivery log. Check that the endpoint is Active and subscribed to the event, that the event has actually happened since you added it, and that WP-Cron is running (the drain only runs when the site has traffic). Failures appear only after the fifth attempt (about half an hour).
  • HTTP 0 in the log. The receiving server didn’t answer within 15 seconds or couldn’t be reached. Check the URL, firewalls and the receiver’s uptime.
  • Signature mismatch on your side. Sign the raw body exactly as received, not re-encoded JSON, and use the X-Attestation-Core-Time header value, not your own clock.

If the Pro licence is inactive, use Settings → Licence to reactivate. See Licensing and updates. Related: Automated email notifications.

Version History

Version 1.0.0 Major October 4, 2026
Author: DHDW