Webhook Event Types
Webhook Event Types
The Snappt Enterprise API sends outbound webhooks to the URLs you register with
POST /webhooks. Each webhook notifies you when something happens on an applicant session:
their documents are submitted, their fraud (and optional income) report is ready, that report
later changes, or an ID verification finishes.
You choose which events a webhook receives through its events array. A webhook is only sent
events whose type appears in that array, so you can point different event types at different URLs.
| Event | Fires when | Retried? |
|---|---|---|
APPLICATION_SUBMITTED | An applicant finishes submitting their documents | No |
REPORT_READY | Snappt finishes reviewing the documents and renders a verdict | Yes, up to 24h |
REPORT_UPDATED | A previously delivered report changes (dispute, appeal, override, or fraud re-review) | No |
IDV_REPORT_READY | An ID verification reaches a terminal status | No |
Retry mechanics, timeouts, and what counts as a successful delivery are described on theWebhook Delivery & Retries page. This page focuses on the event types and their payloads.
The common envelope
Every webhook body, regardless of event type, carries the same top-level envelope fields. The
event-specific content lives under data (and, for REPORT_UPDATED, an additional update
object).
| Field | Type | Description |
|---|---|---|
eventType | string | One of the event types in the table above. |
apiKeyId | uuid | The API key whose webhook is being delivered. |
webhookId | uuid | The webhook that produced this delivery. |
applicantId | uuid | null | The applicant this event is about. null for IDV_REPORT_READY. |
applicantDetailId | uuid | null | The applicant-detail record. null for IDV_REPORT_READY. |
externalId | string | null | Your own identifier for the applicant, echoed back so you can correlate the event with your system. null if you did not set one, and always null on IDV_REPORT_READY. See Correlating events with your system. |
externalMetadata | object | null | Arbitrary metadata you attached to the applicant, echoed back unchanged. null if you did not set any, and always null on IDV_REPORT_READY. |
data | object | Event-specific payload (see each event below). |
update | object | Present only on REPORT_UPDATED. Describes why the report changed. |
Correlating events with your system
Snappt assigns its own ids (applicantId, applicantDetailId, and the entry id in data.id), but
you rarely have those when you first create an applicant. To tie webhook events back to a prospect,
lease, or record in your own system, attach your identifiers when you create the applicant and
Snappt echoes them back on every applicant-scoped event.
Setting externalId and externalMetadata
externalId and externalMetadataBoth are optional and are supplied when you generate an applicant link:
POST /properties/{propertyId}/generate-applicant-linkPOST /embedded/generate-embed-link(Embedded SDK)
| Field | Type | Constraints | Notes |
|---|---|---|---|
externalId | string | Max 255 characters | Your identifier for the applicant (for example, a PMS or CRM prospect id). |
externalMetadata | object | Max 10 KB when serialized to JSON | Arbitrary key/values from your system. Stored and echoed back verbatim; Snappt does not interpret them. |
Example request body:
{
"email": "[email protected]",
"firstName": "Test",
"lastName": "User",
"externalId": "crm-prospect-84321",
"externalMetadata": {
"unitId": "A-204",
"leaseId": "LEASE-99182",
"source": "yardi"
}
}Where they come back
Once set, externalId and externalMetadata are returned:
- in the response of the generate-link endpoint you called;
- on
GET /applicants/{applicantId}(asexternalId/externalMetadata,nullwhen unset); - on every applicant-scoped webhook:
APPLICATION_SUBMITTED,REPORT_READY, andREPORT_UPDATED.
They are not included on IDV_REPORT_READY, which is not tied to an applicant record; those
fields are null there. When you did not set a value, the field is present but null, so the
payload shape stays stable.
APPLICATION_SUBMITTED
Fired when Snappt has received the completed submission of an applicant's documents, before any
review has run. To receive it you create an applicant session, attach documents, and call
POST /submit on the session.
At this point the report has not been reviewed, so data.result and data.status reflect the
applicant's pre-review state (PENDING).
data fields
| Field | Type | Description |
|---|---|---|
id | uuid | The entry (applicant) id. Use this together with eventType as your idempotency key. |
note | string | null | Reviewer note. null at submission time. |
result | string | PENDING at submission. |
status | string | PENDING at submission. |
Delivery: single attempt, not retried.
{
"eventType": "APPLICATION_SUBMITTED",
"data": {
"id": "522e9dd6-5162-4cd4-8428-d88f244ed661",
"note": null,
"result": "PENDING",
"status": "PENDING"
},
"apiKeyId": "3dd44705-1365-4f1e-b167-04830df3627d",
"webhookId": "6ea91907-57b4-446c-b8b3-d4e72c581753",
"applicantId": "522e9dd6-5162-4cd4-8428-d88f244ed661",
"applicantDetailId": "ce05bc52-4dc5-4279-ab47-12db37ecfa53",
"externalId": "crm-prospect-84321",
"externalMetadata": {
"unitId": "A-204",
"leaseId": "LEASE-99182",
"source": "yardi"
}
}REPORT_READY
Fired when Snappt has finished reviewing an applicant's documents for authenticity, and (if
Income Verification is enabled on the property) for income, and has rendered a verdict. This is
always the first report event you receive for an applicant.
data fields
| Field | Type | Description |
|---|---|---|
id | uuid | The entry (applicant) id. |
note | string | null | A human-readable note, populated for some EDITED and UNDETERMINED verdicts (for example, the reason documents could not be accepted). |
result | string | The fraud verdict (see below). |
status | string | READY when the report is complete. |
Result values
The result value depends on your company's result-display setting:
| Meaning | Default display | PASSED/FAILED display |
|---|---|---|
| Documents look authentic | CLEAN | PASSED |
| Evidence of editing/fraud | EDITED | FAILED |
| Could not be determined (for example, "Print to PDF" documents) | UNDETERMINED | UNDETERMINED |
| Not yet reviewed | PENDING | PENDING |
Delivery: retried for up to 24 hours until your endpoint returns HTTP 200. See the delivery
page for the retry cadence.
REPORT_READY (CLEAN / PASSED)
{
"eventType": "REPORT_READY",
"data": {
"id": "b2432c5f-fd72-452e-bdad-ee63ece08a74",
"note": null,
"result": "CLEAN",
"status": "READY"
},
"apiKeyId": "3dd44705-1365-4f1e-b167-04830df3627d",
"webhookId": "6ea91907-57b4-446c-b8b3-d4e72c581753",
"applicantId": "522e9dd6-5162-4cd4-8428-d88f244ed661",
"applicantDetailId": "ce05bc52-4dc5-4279-ab47-12db37ecfa53",
"externalId": "crm-prospect-84321",
"externalMetadata": {
"unitId": "A-204",
"leaseId": "LEASE-99182",
"source": "yardi"
}
}REPORT_READY (EDITED / FAILED)
{
"eventType": "REPORT_READY",
"data": {
"id": "d2530fde-4695-45e6-a288-a2f6d2fafce5",
"note": "additional notes here",
"result": "EDITED",
"status": "READY"
},
"apiKeyId": "3dd44705-1365-4f1e-b167-04830df3627d",
"webhookId": "6ea91907-57b4-446c-b8b3-d4e72c581753",
"applicantId": "f640b76f-3793-43db-8fa9-5c3c843b042a",
"applicantDetailId": "7b4e5a9f-3937-4d15-ade8-3de9c4bcad3e",
"externalId": "crm-prospect-77104",
"externalMetadata": null
}The example above sets an
externalIdbut noexternalMetadata, soexternalMetadatais
present asnull. Fields you never set are always returned asnullrather than omitted.
REPORT_READY (UNDETERMINED)
{
"eventType": "REPORT_READY",
"data": {
"id": "d2530fde-4695-45e6-a288-a2f6d2fafce5",
"note": "The documents you submitted cannot be accepted because they have been downloaded using a process called “Print to PDF”. Please go directly to your financial institution or payroll provider’s website to download the original PDF version of these documents. Do not rename the file.",
"result": "UNDETERMINED",
"status": "READY"
},
"apiKeyId": "3dd44705-1365-4f1e-b167-04830df3627d",
"webhookId": "6ea91907-57b4-446c-b8b3-d4e72c581753",
"applicantId": "f640b76f-3793-43db-8fa9-5c3c843b042a",
"applicantDetailId": "7b4e5a9f-3937-4d15-ade8-3de9c4bcad3e",
"externalId": null,
"externalMetadata": null
}This applicant was created without any external identifiers, so both fields are
null.
REPORT_UPDATED
A report is not always final the first time it is delivered. It can change afterward: an applicant
disputes or appeals an income result, a reviewer applies a manual or calculation override, or a
fraud re-review changes the ruling. Before this event existed, those changes were re-delivered as
another REPORT_READY, which was indistinguishable from a routine redelivery. REPORT_UPDATED
makes a genuine post-delivery change explicit.
When it fires
- The first delivery for an applicant is always
REPORT_READY, neverREPORT_UPDATED. Only a
real change to an already-delivered report producesREPORT_UPDATED. - It fires only on a meaningful change, not on routine review or audit activity:
- Income: an accepted manual or calculation override lands after the last successful
delivery. - Fraud: a re-review produces a different outcome than what was last delivered. A repeat of
the same outcome, or a change that only affects display formatting, does not fire an update.
- Income: an accepted manual or calculation override lands after the last successful
Subscribing
REPORT_UPDATED is delivered alongside REPORT_READY, so a REPORT_UPDATED subscription
requires an active REPORT_READY subscription under the same API key. You can:
- subscribe one webhook to both
REPORT_READYandREPORT_UPDATED(same URL receives both), or - keep
REPORT_READYon one webhook and putREPORT_UPDATEDon a second webhook/URL under the
same API key (split routing).
Attempting to create, or leave, an active REPORT_UPDATED subscription with no active
REPORT_READY subscription under the same API key is rejected with an HTTP 400.
Payload
REPORT_UPDATED carries everything REPORT_READY carries (the same data object, with
data.result reflecting the new outcome), plus an update object.
update field | Type | Description |
|---|---|---|
reason | string | Why the report changed. One of INCOME_DISPUTE (an income dispute or appeal), REPORT_REVISED (a manual/calculation override or a fraud re-review), or OTHER. |
statusDetails | array of { code } | Manual-action status-detail codes (the 4xxx range) present on the current income calculation, the same shape you see in incomeVerificationDetails. May be empty. |
Delivery: single attempt, not retried. Delivery is at-least-once, so you may receive the same
REPORT_UPDATED more than once. Treat duplicates as no-ops (see
Designing an idempotent receiver).
{
"eventType": "REPORT_UPDATED",
"data": {
"id": "b2432c5f-fd72-452e-bdad-ee63ece08a74",
"note": null,
"result": "CLEAN",
"status": "READY"
},
"update": {
"reason": "INCOME_DISPUTE",
"statusDetails": [
{ "code": 4004 }
]
},
"apiKeyId": "3dd44705-1365-4f1e-b167-04830df3627d",
"webhookId": "6ea91907-57b4-446c-b8b3-d4e72c581753",
"applicantId": "522e9dd6-5162-4cd4-8428-d88f244ed661",
"applicantDetailId": "ce05bc52-4dc5-4279-ab47-12db37ecfa53",
"externalId": "crm-prospect-84321",
"externalMetadata": {
"unitId": "A-204",
"leaseId": "LEASE-99182",
"source": "yardi"
}
}The externalId and externalMetadata are identical to the ones on the applicant's original
REPORT_READY, so you can match the update to the same record in your system.
IDV_REPORT_READY
Fired when an ID verification reaches a terminal status. This event is independent of the fraud
and income report events, and its data describes the identity verification rather than a
document report.
data fields
| Field | Type | Description |
|---|---|---|
idVerificationId | uuid | The ID verification this event is about. |
status | string | null | The verification outcome (see below). |
metadata | object | Optional. Present only when the verification carries metadata. |
Status values: PASS, FAIL, PENDING, NEEDS REVIEW, REVIEWING, CANCELED,
INCOMPLETE.
Delivery: single attempt, not retried. On this event the envelope's applicantId,
applicantDetailId, externalId, and externalMetadata are null.
{
"eventType": "IDV_REPORT_READY",
"data": {
"idVerificationId": "a17c9f0e-2b4d-4c8a-9f2e-6d3b1a7c5e40",
"status": "PASS",
"metadata": {}
},
"apiKeyId": "3dd44705-1365-4f1e-b167-04830df3627d",
"webhookId": "6ea91907-57b4-446c-b8b3-d4e72c581753",
"applicantId": null,
"applicantDetailId": null
}Handling report events safely
Because REPORT_READY is retried and both REPORT_READY and REPORT_UPDATED can be delivered
more than once, and because a report can legitimately change after its first delivery, build your
receiver to be idempotent and order-tolerant:
- Use
data.id(the entry/applicant id) pluseventTypeas your idempotency key. A repeated
REPORT_READYwith the same id is a no-op; aREPORT_UPDATEDwith the same id is a legitimate
change to apply. - Do not assume
REPORT_READYis final. A laterREPORT_UPDATEDfor the same applicant supersedes
it; apply the newer outcome andupdate.reason. - Do not rely on ordering. A retry of an older event can arrive after a newer one; compare against
what you have already stored before overwriting. - Return HTTP 200 quickly, then do downstream work after responding.
Updated about 10 hours ago