GuidesRecipesAPI ReferenceChangelog
Log In
Guides

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.

EventFires whenRetried?
APPLICATION_SUBMITTEDAn applicant finishes submitting their documentsNo
REPORT_READYSnappt finishes reviewing the documents and renders a verdictYes, up to 24h
REPORT_UPDATEDA previously delivered report changes (dispute, appeal, override, or fraud re-review)No
IDV_REPORT_READYAn ID verification reaches a terminal statusNo
📘

Retry mechanics, timeouts, and what counts as a successful delivery are described on the

Webhook 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).

FieldTypeDescription
eventTypestringOne of the event types in the table above.
apiKeyIduuidThe API key whose webhook is being delivered.
webhookIduuidThe webhook that produced this delivery.
applicantIduuid | nullThe applicant this event is about. null for IDV_REPORT_READY.
applicantDetailIduuid | nullThe applicant-detail record. null for IDV_REPORT_READY.
externalIdstring | nullYour 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.
externalMetadataobject | nullArbitrary metadata you attached to the applicant, echoed back unchanged. null if you did not set any, and always null on IDV_REPORT_READY.
dataobjectEvent-specific payload (see each event below).
updateobjectPresent 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

Both are optional and are supplied when you generate an applicant link:

  • POST /properties/{propertyId}/generate-applicant-link
  • POST /embedded/generate-embed-link (Embedded SDK)
FieldTypeConstraintsNotes
externalIdstringMax 255 charactersYour identifier for the applicant (for example, a PMS or CRM prospect id).
externalMetadataobjectMax 10 KB when serialized to JSONArbitrary 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} (as externalId / externalMetadata, null when unset);
  • on every applicant-scoped webhook: APPLICATION_SUBMITTED, REPORT_READY, and REPORT_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

FieldTypeDescription
iduuidThe entry (applicant) id. Use this together with eventType as your idempotency key.
notestring | nullReviewer note. null at submission time.
resultstringPENDING at submission.
statusstringPENDING 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

FieldTypeDescription
iduuidThe entry (applicant) id.
notestring | nullA human-readable note, populated for some EDITED and UNDETERMINED verdicts (for example, the reason documents could not be accepted).
resultstringThe fraud verdict (see below).
statusstringREADY when the report is complete.

Result values

The result value depends on your company's result-display setting:

MeaningDefault displayPASSED/FAILED display
Documents look authenticCLEANPASSED
Evidence of editing/fraudEDITEDFAILED
Could not be determined (for example, "Print to PDF" documents)UNDETERMINEDUNDETERMINED
Not yet reviewedPENDINGPENDING

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 externalId but no externalMetadata, so externalMetadata is
present as null. Fields you never set are always returned as null rather 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, never REPORT_UPDATED. Only a
    real change to an already-delivered report produces REPORT_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.

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_READY and REPORT_UPDATED (same URL receives both), or
  • keep REPORT_READY on one webhook and put REPORT_UPDATED on 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 fieldTypeDescription
reasonstringWhy 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.
statusDetailsarray 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

FieldTypeDescription
idVerificationIduuidThe ID verification this event is about.
statusstring | nullThe verification outcome (see below).
metadataobjectOptional. 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) plus eventType as your idempotency key. A repeated
    REPORT_READY with the same id is a no-op; a REPORT_UPDATED with the same id is a legitimate
    change to apply.
  • Do not assume REPORT_READY is final. A later REPORT_UPDATED for the same applicant supersedes
    it; apply the newer outcome and update.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.

Did this page help you?