Product that suits modern B2B Tech companies

Book Demo
B
Book demo call-to-action illustration
BACK
B

UKG Pro webhooks: subscriptions, HMAC verification, and missed events

Platform APIs
September 21, 2026
Summarise the blog with AI
Open in ChatGPT
Ask questions about this page
Open in Claude
Ask questions about this page

Key takeaways

  • A UKG Pro webhook subscription is a stored configuration: a name, an endpoint, a list of eventAliases, an optional HMAC secret and custom headers.
  • The HMAC secret is optional in UKG. Set one anyway. Without it, your endpoint accepts anything posted to it.
  • UKG signs using the Svix / Standard Webhooks scheme: HMAC-SHA256 over message ID, timestamp and raw body, sent in webhook-id, webhook-timestamp and webhook-signature.
  • Acknowledge fast, deduplicate on webhook-id, and process in the background, so retries and replays can't double-process.
  • Delivery is near real-time, not guaranteed. Events are kept 14 days, the free tier caps at 10,000 a month, and UKG recommends a polling or nightly-report backstop.

UKG Pro webhooks: subscriptions, HMAC verification, and missed events

UKG Pro can POST a notification to your endpoint soon after an employee's benefit election, job assignment or life event changes. It won't promise the notification arrives.

Build a consumer that trusts every payload, and you have no defense against a forged request. Build one that assumes delivery is guaranteed, and a single dropped event leaves your data out of sync until something downstream breaks.

This guide covers the whole path in order: creating a subscription in the UI or the Premium API, verifying signatures correctly, building a receiver that survives retries, and designing the reconciliation UKG itself recommends for events that never show up. For how UKG Pro API authentication works more broadly, see our UKG Pro API authentication guide.

What a UKG Pro webhook subscription is

A subscription is a stored configuration, not a one-time hookup. When an event fires in UKG Pro, UKG checks every active subscription's event list and POSTs a notification to each matching endpoint.

Each subscription carries:

  • name and description, so you can find it later.
  • endpoint, the URL UKG posts to.
  • eventAliases, the events it reacts to.
  • hmacSecret, optional, at least 24 characters.
  • headers, any custom headers you want on each POST.
  • isDisabled, which defaults to false.

That last default matters when you're testing. A new subscription is live the moment you save it unless you create it inactive, so a half-built endpoint can start receiving production events immediately. You can deactivate a subscription to pause it without losing its setup, or delete it outright.

Creating a subscription: the UI and the Premium API

UKG Webhooks is tier based. Every tenant gets a free tier, and Webhooks Premium adds volume and an API:

Free tierWebhooks Premium
VolumeUp to 10,000 event notifications per calendar monthBeyond 10,000 per month
Managing subscriptionsUKG Webhooks UIUI plus the Webhooks Premium REST API
Retrieve and replay messagesNoYes, by subscription and date range
Replay audit logs and endpoint tests via APINoYes
Event retention14 days14 days
Source: UKG Webhooks User Guide and the Webhooks Premium page.

Through the UI

  1. Name it something you or a teammate can identify later.
  2. Enter the endpoint URL UKG should POST to.
  3. Add an HMAC secret of at least 24 characters. Copy it now: once the subscription is saved, UKG won't show it again, and you can only generate a new one.
  4. Pick your events and move them to Selected Events.
  5. Save it as inactive if you're still testing, then send a test event before going live.

Through the Premium API

The Webhooks Premium API covers everything the UI does, plus message retrieval, replay and audit. Authenticate with a UKG bearer token (see UKG's guide to obtaining bearer tokens) and send the global-tenant-id header on every call:

curl · Create a subscription (Webhooks Premium API)
curl -X POST "https://{hostname}/webhooks/v1/subscriptions" \
  -H "Authorization: Bearer {access_token}" \
  -H "global-tenant-id: {global_tenant_id}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "benefits-sync-prod",
    "description": "Employee and benefit changes for the benefits platform",
    "endpoint": "https://api.example.com/hooks/ukg",
    "isDisabled": true,
    "eventAliases": ["{event_alias}"],
    "hmacSecret": "{a_secret_of_at_least_24_characters}"
  }'

# 202 Accepted. The response includes subscriptionId and the hmacSecret, encoded.
# Store that encoded secret now: no later call returns it.
# isDisabled defaults to false. Create as true, test, then PATCH it to false.
OperationMethod and pathNotes
List available eventsGET /webhooks/v1/eventsWhere your eventAliases come from
Create a subscriptionPOST /webhooks/v1/subscriptionsReturns 202 with a subscriptionId and the encoded hmacSecret
Get, update or delete oneGET, PUT, PATCH, DELETE /webhooks/v1/subscriptions/{subscription-id}PATCH changes endpoint, isDisabled or eventAliases only
Send a test eventPOST /webhooks/v1/subscriptions/{subscription-id}/testUse before you point real events at a new endpoint
List a subscription's messagesGET /webhooks/v3/subscriptions/{subscription-id}/messagesFilter by date range (up to 2 weeks) and status: failed or all
Replay messagesPOST /webhooks/v3/subscriptions/{subscription-id}/messages/replaysBody: status, sinceDateTime, untilDateTime
Check a replayGET /webhooks/v3/subscriptions/{subscription-id}/messages/replays/{replay-id}/audit-logsConfirms what was redelivered
Base URL: https://{hostname}. Send a bearer token and the global-tenant-id header on every call. Premium only.

A 404 on an update almost always means the subscription ID is wrong, not that something is broken.

Verifying the signature

If you set a secret, UKG signs every notification with HMAC-SHA256, following the Svix verification scheme that its user guide points to (formalized as Standard Webhooks). The signature travels in three headers:

HeaderCarries
webhook-idA unique ID for the message. The first part of the signed content, and your deduplication key.
webhook-timestampUnix time the message was sent. The second part of the signed content, and your replay check.
webhook-signatureOne or more signatures, space separated, each in the form v1,<base64 HMAC-SHA256>
UKG follows the Svix / Standard Webhooks scheme and replaces the svix prefix with webhook. Tools built on Svix may show svix-id, svix-timestamp and svix-signature instead.

Verification takes five steps, and most broken receivers fail the first one:

  1. Read the raw body first. Capture the bytes before any JSON parsing or middleware touches them. Re-serializing parsed JSON changes the bytes, and one changed character invalidates the signature.
  2. Build the signed content: the webhook-id value, a period, the webhook-timestamp value, a period, then the raw body.
  3. Compute HMAC-SHA256 over that content with your secret, and base64-encode the result.
  4. Compare in constant time against each v1, value in webhook-signature. The header can hold more than one signature during secret rotation.
  5. Reject stale timestamps. Standard Webhooks recommends a 5-minute tolerance as replay protection. UKG doesn't enforce this for you.
Python · Verify a UKG webhook signature
import base64, hashlib, hmac, time

TOLERANCE_SECONDS = 300  # Standard Webhooks recommendation; UKG doesn't enforce one for you

def verify_ukg_webhook(headers, raw_body: bytes, secret: str) -> bool:
    msg_id = headers.get("webhook-id")
    timestamp = headers.get("webhook-timestamp")
    signature_header = headers.get("webhook-signature")
    if not (msg_id and timestamp and signature_header):
        return False  # unsigned or malformed: reject

    # 1. Replay protection
    if abs(time.time() - int(timestamp)) > TOLERANCE_SECONDS:
        return False

    # 2. Key: use the encoded secret UKG returned at creation.
    #    Standard Webhooks secrets are base64, prefixed with "whsec_".
    if secret.startswith("whsec_"):
        key = base64.b64decode(secret[len("whsec_"):])
    else:
        key = secret.encode()  # confirm against a test event if yours has no prefix

    # 3. Sign id.timestamp.raw_body. Never re-serialize parsed JSON.
    signed_content = f"{msg_id}.{timestamp}.".encode() + raw_body
    expected = base64.b64encode(
        hmac.new(key, signed_content, hashlib.sha256).digest()
    ).decode()

    # 4. The header can hold several "v1,<sig>" values separated by spaces.
    for item in signature_header.split(" "):
        version, _, sig = item.partition(",")
        if version == "v1" and hmac.compare_digest(sig, expected):
            return True
    return False
Node.js · Verify a UKG webhook signature (Express)
const crypto = require("crypto");
const express = require("express");
const app = express();

const SECRET = process.env.UKG_WEBHOOK_SECRET; // the encoded secret from creation
const TOLERANCE_SECONDS = 300;

function keyFrom(secret) {
  return secret.startsWith("whsec_")
    ? Buffer.from(secret.slice(6), "base64")
    : Buffer.from(secret, "utf8");
}

app.post("/hooks/ukg", express.raw({ type: "*/*" }), (req, res) => {
  const id = req.header("webhook-id");
  const ts = req.header("webhook-timestamp");
  const sigHeader = req.header("webhook-signature");
  if (!id || !ts || !sigHeader) return res.status(401).end();

  if (Math.abs(Date.now() / 1000 - Number(ts)) > TOLERANCE_SECONDS) {
    return res.status(401).end();
  }

  const signed = Buffer.concat([Buffer.from(`${id}.${ts}.`), req.body]); // raw bytes
  const expected = crypto.createHmac("sha256", keyFrom(SECRET)).update(signed).digest("base64");

  const ok = sigHeader.split(" ").some((item) => {
    const [version, sig] = item.split(",");
    if (version !== "v1" || !sig) return false;
    const a = Buffer.from(sig);
    const b = Buffer.from(expected);
    return a.length === b.length && crypto.timingSafeEqual(a, b);
  });
  if (!ok) return res.status(401).end();

  res.status(200).end();           // acknowledge fast
  enqueue({ id, body: req.body });  // dedupe by id and process in a worker
});

For background on why HMAC is the right mechanism here, see how HMAC secures webhooks.

Building a receiver that survives retries

A verified payload is only half the job. What your endpoint does next decides whether a retry or replay corrupts your data or just arrives twice.

  1. Acknowledge fast. Return a 2xx as soon as you've verified and stored the raw payload, before any real processing. Senders retry on timeouts.
  2. Deduplicate on webhook-id. Record every ID you've processed and skip repeats. That's the same idea as the IETF draft on idempotency keys, and it's what makes Premium replays safe.
  3. Process asynchronously. Hand the payload to a queue or background job. A slow database write shouldn't be able to trigger a timeout and a retry you then have to deduplicate anyway.

Get this right and retries become non-events. They still don't solve the harder problem: an event that never arrives at all.

When events go missing, and how to reconcile

UKG keeps webhook events for 14 days, and its own guide is explicit that some events won't be delivered. The reasons it lists are ordinary:

  • Disabled triggers. UKG webhooks depend on database triggers in UKG Pro. If consultants or internal developers disable or remove those triggers, or a database restore runs, events during that window are never sent.
  • Your own mass updates. UKG suggests marking subscriptions inactive while you run a mass update to your company data, so you aren't flooded. Anything that changes in that window won't arrive as an event.
  • Your endpoint being down longer than the sender keeps retrying.

A receiver can't detect any of these on its own, however well it verifies and deduplicates. So you need a second path back to the truth:

  • Secondary polling or a nightly report. This is UKG's own recommendation for integrations with zero tolerance for missed events. Pull current state on a schedule and diff it against what you have.
  • Premium replay. With Webhooks Premium, list failed messages for a window of up to two weeks and replay them:
curl · Find failed messages and replay them (Premium)
# 1. List failed messages for a window of up to 2 weeks
curl "https://{hostname}/webhooks/v3/subscriptions/{subscription-id}/messages?since-date-time=2026-09-14T00:00:00Z&until-date-time=2026-09-21T00:00:00Z&status=failed" \
  -H "Authorization: Bearer {access_token}" \
  -H "global-tenant-id: {global_tenant_id}"

# 2. Replay them
curl -X POST "https://{hostname}/webhooks/v3/subscriptions/{subscription-id}/messages/replays" \
  -H "Authorization: Bearer {access_token}" \
  -H "global-tenant-id: {global_tenant_id}" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "failed",
    "sinceDateTime": "2026-09-14T00:00:00Z",
    "untilDateTime": "2026-09-21T00:00:00Z"
  }'
  • Planned blackout windows. When you pause subscriptions for a mass update, schedule a full reconciliation for right after it.

The pattern holds for every HR system, not just UKG. Webhooks tell you something changed sooner. A scheduled sync makes sure you never miss it.

Where Bindbee fits

If you'd rather not build and maintain this consumer, plus the equivalent for every other payroll and HR system your product touches, that's what a unified API is for. Bindbee connects to 67+ HRIS, payroll, ATS and benefits systems through one API, UKG Pro among them. Its UKG Pro connector uses a service account your customer's admin sets up, following a published guide.

Bindbee is built around the scheduled-sync side of this pattern. Each connection syncs every 24 hours by default, adjustable per connection, and Bindbee sends your product a webhook when a sync finishes or finds created or updated records. Those webhooks are signed too, with HMAC-SHA256 over the raw body, so the same keep-the-raw-bytes rule applies.

Because that sync carries employee and benefits data, Bindbee is SOC 2 Type II and ISO 27001 certified, HIPAA and GDPR compliant, and offers a standard BAA template, negotiated for enterprise agreements. Benefits platforms including Newfront and Papershift run on it today.

We build the integrations. You build the product.

FAQ

Does UKG guarantee webhook delivery?

No. UKG describes webhooks as near real-time and keeps events for 14 days. Events can be missed, for example when database triggers are disabled during a restore, and UKG recommends a secondary polling sync or nightly report for integrations that can't tolerate a missed event.

What headers does UKG use for webhook signatures?

webhook-id, webhook-timestamp and webhook-signature. UKG follows the Svix verification scheme and replaces the svix prefix with webhook, so Svix-based tools may show svix-id, svix-timestamp and svix-signature for the same values.

Is the HMAC secret required on UKG webhooks?

No, UKG marks it optional, but you should always set one. It must be at least 24 characters, and UKG won't show it again after the subscription is saved, so store it at creation.

Can I replay UKG webhooks I missed?

Yes, with Webhooks Premium. The Premium API lists a subscription's messages by date range and status, and replays them for redelivery. The free tier has no replay, so rely on polling or a nightly report to backfill gaps.

How many webhook events does the UKG free tier include?

Up to 10,000 event notifications per calendar month. Beyond that you need Webhooks Premium.

Kunal Tyagi
CTO
Bindbee
VIEW AUTHOR
BLOG_

Related blogs