Skip to main content

Webhook Payload Reference

The envelope every Oho webhook delivery uses, and a field-by-field breakdown of each event type's data body. Reach for this page when you're writing the handler that consumes an event, or working out which field carries the outcome you need.

New to Oho webhooks? Start with Webhooks & Event Delivery — it covers subscribing, signature verification, and testing. GET /webhooks/events returns the live catalogue of event types, which is authoritative if it ever disagrees with this page.

Every payload uses the same envelope:

{
"deliveryId": "<uuid>",
"eventType": "credential.verified",
"emittedAt": "<iso-8601>",
"entityUrn": "urn:li:credential:...",
"tenant": { "id": "xref-poc", "url": "https://xref-poc.weareoho.com" },
"data": {/* event-specific */}
}
FieldDescription
deliveryIdStable per-event UUID. Sent in the X-Oho-Delivery header too. Use it as your idempotency key — retries of the same event re-use the same id.
eventTypeSame string as in your subscription.
emittedAtWhen the event was generated server-side (ISO 8601, nanosecond precision).
entityUrnThe Oho URN of the entity the event is about.
tenantWhich Oho deployment sent this webhook: tenant.id is the deployment slug (e.g. xref-poc), tenant.url its base URL. Present on every event; also sent (id only) in the X-Oho-Tenant header. Route on it when one endpoint receives events from more than one Oho instance.
dataEvent-specific body. See examples below.

credential.verified

credential.verified fires on every terminal verification outcome — success (MAY_ENGAGE), failure (MAY_NOT_ENGAGE), or REVIEW_REQUIRED. It does not fire for IN_PROGRESS (not terminal) or ERROR (transient — retries are expected, so emitting would create noise).

A payload from a successful VIC WWC verification (MAY_ENGAGE / VALID):

{
"deliveryId": "0167d799-f51c-41a9-a777-58a65bb7d305",
"eventType": "credential.verified",
"emittedAt": "2026-06-09T02:23:35.496341557Z",
"entityUrn": "urn:li:credential:wwcc-vic-1234567A",
"tenant": { "id": "xref-poc", "url": "https://xref-poc.weareoho.com" },
"data": {
"success": true,
"eligibility": "MAY_ENGAGE",
"statusDetail": "VALID",
"verifiedAt": "2026-06-09T02:23:35.486984338Z",
"entityCreatedAt": "2024-01-15T03:22:10.114Z",
"credential": {
"credentialUrn": "urn:li:credential:wwcc-vic-1234567A",
"credentialId": "wwcc-vic-1234567A",
"verifiedCredentialUrn": "urn:li:verifiedCredential:vicwwc-VIC-1234567A",
"credentialType": "vicwwc",
"type": "wwcc",
"jurisdiction": "VIC",
"identifier": "1234567A",
"externalReferenceId": "HRIS-99213",
"holder": {
"firstName": "Jane",
"middleName": "Mary",
"lastName": "Smith"
},
"issueDate": "2024-01-15",
"expiryDate": "2029-01-15",
"displayName": "WWCC - VIC",
"category": "SCREENING",
"status": "ACTIVE",
"issuingAuthority": "Services Victoria",
"countryOfIssue": "Australia",
"customFieldValues": [{ "label": "Cost centre", "value": "CC-4471" }]
},
"registry": {
"authority": "Services Victoria",
"expiryDate": "2029-01-15",
"cardType": "Employee",
"holderName": "SMITH, Jane Mary",
"identifier": "1234567A",
"flags": { "conditions": "NONE" }
},
"owners": {
"applicants": [
{
"urn": "urn:li:applicant:app_8d3c2e91",
"id": "app_8d3c2e91",
"externalId": "ATS-55021"
}
],
"workers": []
},
"request": {
"source": "api",
"correlationId": "req_7f3a9c22"
},
"changes": {}
}
}

What each data.* field carries:

FieldDescription
successtrue only when eligibility is MAY_ENGAGE; false for every other outcome, including REVIEW_REQUIRED. A convenience flag — drive real business logic from eligibility and statusDetail.
eligibilityThe primary compliance signal: MAY_ENGAGE, MAY_NOT_ENGAGE, REVIEW_REQUIRED. (IN_PROGRESS / ERROR never reach a webhook.)
statusDetailThe reason behind the signal: VALID, EXPIRING_SOON, EXPIRED, NOT_CURRENT, REVOKED, NOT_FOUND, CONDITIONS_TO_REVIEW, PENDING_DECISION, etc.
verifiedAtWhen Oho recorded the outcome (ISO 8601).
entityCreatedAtWhen the credential record was first created in Oho (ISO 8601). Stamped on the credential at creation and persisted, so it is stable and present on every credential.* event (verified, added, updated, and the ownership events). Records created before this field existed are backfilled on their next create-time write or verification — from the first-write timestamp when available, otherwise the earliest date otherwise known on the record.
credential.credentialUrn / credentialIdThe Oho URN of the credential claim and its bare id.
credential.verifiedCredentialUrnThe URN of the verification result, paired with the claim.
credential.credentialTypeThe canonical verifier code — vicwwc, nswwwc, qldblue, ahpra, etc.
credential.typeThe broader family — wwcc, teacher, health, etc. Absent when the family is unknown; fall back to credentialType.
credential.jurisdictionState / national code — VIC, NSW, AUS.
credential.identifierThe card / registration number you submitted (distinct from registry.identifier, which is what the registry returned).
credential.externalReferenceIdOptional. Your own ID for this credential in your upstream system, if you supplied one. Lets you match back without keeping an Oho-ID map.
credential.holderStructured name parts (firstName, middleName, lastName) as Oho has them on file. middleName present only when known.
credential.issueDate / expiryDateOptional, ISO 8601. Prefer the registry's value from this run; fall back to the stored credential value. Omitted if neither is known.
credential.displayNameOptional. Human-readable label, e.g. WWCC - VIC.
credential.categoryOptional. High-level grouping: SCREENING, GOVERNMENT_ID, LICENSE, CERTIFICATION.
credential.statusOptional. Card lifecycle state — ACTIVE, EXPIRED, REVOKED, SUSPENDED, PENDING, REVIEW_REQUIRED — reflecting the just-computed outcome. Distinct from eligibility (the compliance signal).
credential.issuingAuthorityOptional. Who issued the credential (e.g. VicRoads). Distinct from registry.authority (who Oho verified it against), which may differ.
credential.countryOfIssueOptional. Issuing country — mainly for passport / travel-document (VEVO) credentials.
credential.customFieldValuesOptional. Admin-defined custom fields on the credential's type, as {label, value} entries. Present only for custom types that define them.
registryWhat the issuing authority returned. authority is the display name; expiryDate, cardType, holderName, identifier, and a free-form flags map are each present only when the registry supplied them.
ownersObject grouped by owner type: owners.applicants and owners.workers. Both keys are always present (empty array when none). Each entry carries the full urn, the bare id (app_… / wkr_…), and — best-effort — externalId, the owner's id in your own system (Workday Employee_ID, ATS reference, etc.).
requestOptional block. source (e.g. api, manual_ui, auto_verify, ongoing, ingestion) and the caller's correlationId, echoed so you can match the delivery back to the originating request.
changesField-level diff against the prior verification (e.g. MAY_ENGAGEEXPIRED). The key is omitted entirely on the first-ever verification (no prior to compare); present-but-empty ({}) on a re-verify where nothing changed.
owners is an object, not an array

Earlier drafts showed owners as a flat array of ids (["app_8d3c2e91"]). The real payload is the grouped { "applicants": [...], "workers": [...] } object above. Index owners.applicants / owners.workers.

The exact set of fields under credential, registry, and changes can vary by credential type — different registries return different metadata. Treat unknown fields as forward-compatible additions.

credential.added / credential.updated

credential.added fires when a credential record is first created — any type, verifiable or custom. credential.updated fires when its stored fields are edited, and excludes verification-driven changes (those publish as credential.verified) and ownership changes (those publish as the ownership events below).

The data body is the same credential / registry / owners / request shape as credential.verified, minus the verification-run fields (success, eligibility, statusDetail, verifiedAt at the top level, and changes). A credential.added for a pre-verified import:

{
"deliveryId": "d401cbb0-3fff-4157-9a25-96f253e63166",
"eventType": "credential.added",
"emittedAt": "2026-09-14T02:58:01.688679267Z",
"entityUrn": "urn:li:credential:cred_640386fa53146ce3",
"tenant": { "id": "xref-poc", "url": "https://xref-poc.weareoho.com" },
"data": {
"entityCreatedAt": "2026-08-28T01:17:49.395900507Z",
"credential": {
"credentialUrn": "urn:li:credential:cred_640386fa53146ce3",
"credentialId": "cred_640386fa53146ce3",
"verifiedCredentialUrn": "urn:li:verifiedCredential:vevo-AUS-EJ4354478",
"displayName": "VEVO Work Entitlement",
"credentialType": "VEVO",
"type": "vevo",
"category": "SCREENING",
"jurisdiction": "AUS",
"identifier": "EJ4354478",
"externalReferenceId": null,
"isVerifiable": true,
"status": "ACTIVE",
"holder": {
"firstName": "Hillary",
"middleName": null,
"lastName": "Song"
},
"issueDate": "2023-02-01",
"expiryDate": null,
"eligibility": "MAY_ENGAGE",
"statusDetail": "VALID",
"issuingAuthority": null,
"countryOfIssue": "China",
"customFieldValues": null
},
"registry": {
"authority": null,
"expiryDate": null,
"cardType": "Temporary Visa",
"holderName": "SONG, Hillary",
"identifier": "EJ4354478",
"flags": {
"work_entitlement": "Unlimited",
"visa_class": "500",
"grant_date": "2023-02-01"
}
},
"owners": {
"applicants": [],
"workers": [
{
"urn": "urn:li:worker:wkr_c2978381f16f1c10",
"id": "wkr_c2978381f16f1c10",
"externalId": "40410"
}
]
},
"request": { "source": "manual_emit" }
}
}

The fields carry the same meanings as in the credential.verified table above, with these differences:

FieldDescription
credential.credentialTypeThe credential's stored type code as Oho holds it (e.g. VEVO, WWCC_VIC) rather than the verifier code credential.verified reports. credential.type is the family in both, so branch on that when you want one code path.
credential.eligibility / statusDetailThe current verification snapshot carried inside the credential block (not at the top level as on credential.verified). Null on a freshly created credential no registry has answered for.
credential.verifiedCredentialUrnThe verification this credential is linked to, or null when no registry has ever answered for it.
registryThe last stored registry answer, in the same shape credential.verified publishes — authority, expiryDate, cardType, holderName, identifier, and the free-form flags map. null (the whole block) when no registry has ever answered: a custom type, or a credential created moments ago.
request.sourceHow the record was written — ingestion, edit, api, manual_resend, etc. No correlationId: there is no synchronous verification request to correlate against.
A null registry block is not the same as an empty one

"registry": null means no registry has ever been asked. A block that is present with null members means the registry was asked and returned nothing for those fields. If you branch on registry flags, check the block for null first.

recruitmentCheck.completed

{
"deliveryId": "8a0d5e3f-c4a2-4b6e-9c7d-1f2e3a4b5c6d",
"eventType": "recruitmentCheck.completed",
"emittedAt": "2026-06-09T03:14:22.118Z",
"entityUrn": "urn:li:credentialCheck:chk_2bX9pK4mN1qR8sT3",
"tenant": { "id": "xref-poc", "url": "https://xref-poc.weareoho.com" },
"data": {
"checkId": "chk_2bX9pK4mN1qR8sT3",
"checkUrn": "urn:li:credentialCheck:chk_2bX9pK4mN1qR8sT3",
"applicantUrn": "urn:li:applicant:app_8d3c2e91",
"screeningPackageCode": "oho0001",
"status": "COMPLETED",
"submittedAt": "2026-06-09T03:14:21.802Z",
"completedAt": "2026-06-09T03:14:22.118Z",
"credentials": [
{
"credentialId": "cred_5fA1bC2d",
"credentialUrn": "urn:li:credential:cred_5fA1bC2d",
"credentialType": "WWCC_NSW",
"displayName": "WWCC - NSW",
"status": "ACTIVE",
"eligibility": "MAY_ENGAGE",
"statusDetail": "VALID",
"verificationAuthority": "Service NSW",
"lastVerifiedDate": "2026-06-09T03:14:21.900Z",
"expiryDate": "2029-01-15",
"referenceNumber": "WWC1234567E",
"jurisdiction": "NSW"
}
],
"policeChecks": [
{
"policeCheckId": "pc_9zY8xW7v",
"policeCheckUrn": "urn:li:policeCheck:pc_9zY8xW7v",
"displayName": "NPC - Jane Doe - 2026-06-09",
"kind": "NPC",
"provider": "NCC",
"externalId": "ncc_abc123",
"expiryDate": "2029-06-09",
"decision": "SUITABLE",
"result": "NDCO",
"checkStatus": "complete",
"resultDate": "2026-06-09",
"reviewRequired": false,
"pending": false
}
],
"exemptions": [
{
"exemptionId": "exm_3kL8dF2a",
"exemptionUrn": "urn:li:exemption:exm_3kL8dF2a",
"exemptionType": "BLUE_CARD_EXEMPTION",
"exemptsFromCredentialType": "BLUE_CARD",
"displayName": "QLD Blue Card Exemption",
"status": "ACTIVE",
"eligibility": "MAY_ENGAGE",
"statusDetail": "VALID",
"verificationAuthority": "Blue Card Services",
"lastVerifiedDate": "2026-06-09T03:14:21.960Z",
"referenceNumber": "1986805/1",
"expiryDate": "2027-01-15",
"jurisdiction": "QLD",
"grantingAuthority": "Blue Card Services"
}
],
"declarations": [
{
"declarationId": "decl_6mP2qR9t",
"declarationUrn": "urn:li:declaration:decl_6mP2qR9t",
"declarationCode": "CUSTOM_decl0001",
"displayName": "Code of Conduct Acknowledgement",
"status": "ACTIVE",
"acceptedAt": "2026-06-09T03:14:15Z",
"acceptedBy": "urn:li:applicant:app_8d3c2e91",
"signedAt": "2026-06-09T03:14:16Z"
}
],
"bans": {
"status": "CLEAR",
"lastCheckedAt": "2026-06-09T03:14:22.000Z",
"matchedVerifiedBanUrns": [],
"weakSignalMatchedUrns": [],
"registriesChecked": ["AGED_CARE_QUALITY"],
"consentRecordedAt": "2026-06-09T03:14:21.802Z"
}
}
}

The payload groups results by type:

  • credentials[] — each verifiable credential the applicant submitted, with its latest verification outcome (eligibility, statusDetail, verificationAuthority, lastVerifiedDate).
  • policeChecks[] — National/International Police Check results (decision, result, reviewRequired).
  • exemptions[] — the applicant's active exemptions (dispensations from holding a normally-required credential), with the same verification-outcome fields as credentials.
  • declarations[] — the applicant's active declaration acceptances (Code of Conduct, Right to Work, etc.) with acceptedAt / acceptedBy and, when signed on the capture form, signedAt.
  • bans — a single summary object: overall ban-check status (CLEAR / REVIEW_REQUIRED / MATCH_FOUND / CHECK_FAILED / PENDING), any matchedVerifiedBanUrns, and the registriesChecked the applicant consented to on this check.

Exemptions and declarations link to the applicant rather than to a specific check, so they reflect the applicant's active records at completion time. Each array (or the bans object) is omitted entirely when there's nothing of that type, and individual fields are omitted when unset — treat them all as forward-compatible additions.

Ownership events: credential.linked / credential.unlinked / credential.transferred

These fire when a credential's ownership changes via the POST /credentials/{id}/link, /unlink, and /transfer endpoints (and their bulk variants). They share the exact credential / registry / owners / request shape of every other credential.* event, and add a changes block describing the owner-slot diff in the same {from, to} form as credential.verified. The owners block always reflects the post-change state, and registry carries the last stored registry answer for the credential (null when none) — so a credential.linked for an already-verified credential tells you what the registry said without a second call.

credential.transferred for the canonical applicant→worker hire flow — the applicant slot was cleared and the worker slot set:

{
"deliveryId": "3f1b2c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"eventType": "credential.transferred",
"emittedAt": "2026-06-09T04:05:12.220Z",
"entityUrn": "urn:li:credential:cred_5fA1bC2d",
"tenant": { "id": "xref-poc", "url": "https://xref-poc.weareoho.com" },
"data": {
"entityCreatedAt": "2024-01-15T03:22:10.114Z",
"credential": {
"credentialUrn": "urn:li:credential:cred_5fA1bC2d",
"credentialId": "cred_5fA1bC2d",
"displayName": "WWCC - NSW",
"credentialType": "nswwwc",
"type": "wwcc",
"jurisdiction": "NSW",
"identifier": "WWC1234567E",
"isVerifiable": true,
"status": "ACTIVE"
},
"registry": {
"authority": "Service NSW",
"expiryDate": "2029-01-15",
"cardType": "Employee",
"holderName": "DOE, Jane",
"identifier": "WWC1234567E",
"flags": { "conditions": "NONE" }
},
"owners": {
"applicants": [],
"workers": [
{
"urn": "urn:li:worker:wrk_9a1b",
"id": "wrk_9a1b",
"externalId": "EMP-4471"
}
]
},
"request": { "source": "api" },
"changes": {
"applicantUrn": { "from": "urn:li:applicant:app_8d3c2e91", "to": null },
"workerUrn": { "from": null, "to": "urn:li:worker:wrk_9a1b" }
}
}
}

For credential.linked the changes block carries only the slot that was set ("from": null); for credential.unlinked, only the slot that was cleared ("to": null).

webhook.test

Fired by POST /webhooks/{id}/ping — use it to validate your endpoint end-to-end without waiting for a real event.

{
"deliveryId": "c4a2-...",
"eventType": "webhook.test",
"emittedAt": "2026-06-09T03:20:00Z",
"entityUrn": "urn:li:webhook:whk_2bX9pK4mN1qR8sT3",
"tenant": { "id": "xref-poc", "url": "https://xref-poc.weareoho.com" },
"data": {
"subscriptionId": "whk_2bX9pK4mN1qR8sT3",
"message": "Synthetic test event delivered by POST /webhooks/{id}/ping",
"deliveredAt": "2026-06-09T03:20:00Z"
}
}

Where to go next