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 */}
}
| Field | Description |
|---|---|
deliveryId | Stable 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. |
eventType | Same string as in your subscription. |
emittedAt | When the event was generated server-side (ISO 8601, nanosecond precision). |
entityUrn | The Oho URN of the entity the event is about. |
tenant | Which 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. |
data | Event-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:
| Field | Description |
|---|---|
success | true 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. |
eligibility | The primary compliance signal: MAY_ENGAGE, MAY_NOT_ENGAGE, REVIEW_REQUIRED. (IN_PROGRESS / ERROR never reach a webhook.) |
statusDetail | The reason behind the signal: VALID, EXPIRING_SOON, EXPIRED, NOT_CURRENT, REVOKED, NOT_FOUND, CONDITIONS_TO_REVIEW, PENDING_DECISION, etc. |
verifiedAt | When Oho recorded the outcome (ISO 8601). |
entityCreatedAt | When 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 / credentialId | The Oho URN of the credential claim and its bare id. |
credential.verifiedCredentialUrn | The URN of the verification result, paired with the claim. |
credential.credentialType | The canonical verifier code — vicwwc, nswwwc, qldblue, ahpra, etc. |
credential.type | The broader family — wwcc, teacher, health, etc. Absent when the family is unknown; fall back to credentialType. |
credential.jurisdiction | State / national code — VIC, NSW, AUS. |
credential.identifier | The card / registration number you submitted (distinct from registry.identifier, which is what the registry returned). |
credential.externalReferenceId | Optional. 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.holder | Structured name parts (firstName, middleName, lastName) as Oho has them on file. middleName present only when known. |
credential.issueDate / expiryDate | Optional, ISO 8601. Prefer the registry's value from this run; fall back to the stored credential value. Omitted if neither is known. |
credential.displayName | Optional. Human-readable label, e.g. WWCC - VIC. |
credential.category | Optional. High-level grouping: SCREENING, GOVERNMENT_ID, LICENSE, CERTIFICATION. |
credential.status | Optional. Card lifecycle state — ACTIVE, EXPIRED, REVOKED, SUSPENDED, PENDING, REVIEW_REQUIRED — reflecting the just-computed outcome. Distinct from eligibility (the compliance signal). |
credential.issuingAuthority | Optional. Who issued the credential (e.g. VicRoads). Distinct from registry.authority (who Oho verified it against), which may differ. |
credential.countryOfIssue | Optional. Issuing country — mainly for passport / travel-document (VEVO) credentials. |
credential.customFieldValues | Optional. Admin-defined custom fields on the credential's type, as {label, value} entries. Present only for custom types that define them. |
registry | What 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. |
owners | Object 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.). |
request | Optional 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. |
changes | Field-level diff against the prior verification (e.g. MAY_ENGAGE → EXPIRED). 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 arrayEarlier 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:
| Field | Description |
|---|---|
credential.credentialType | The 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 / statusDetail | The 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.verifiedCredentialUrn | The verification this credential is linked to, or null when no registry has ever answered for it. |
registry | The 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.source | How the record was written — ingestion, edit, api, manual_resend, etc. No correlationId: there is no synchronous verification request to correlate against. |
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.) withacceptedAt/acceptedByand, when signed on the capture form,signedAt.bans— a single summary object: overall ban-checkstatus(CLEAR/REVIEW_REQUIRED/MATCH_FOUND/CHECK_FAILED/PENDING), anymatchedVerifiedBanUrns, and theregistriesCheckedthe 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
- Webhooks & Event Delivery — subscribe, verify signatures, rotate the secret, read delivery history
- Recovering a Broken Webhook Endpoint — the runbook for after an outage
- API Reference — every endpoint, field, and query parameter