# Customer Event

> The Customer webhook fires when a customer completes Noah's hosted onboarding form and whenever their hosted onboarding status changes.

## Overview

The `Customer` webhook event is triggered when a customer completes Noah's hosted onboarding form, after being directed to the hosted URL returned by the [Hosted Onboarding API](https://docs.noah.com/api-reference/create-onboarding-session), as well as when the customer's hosted onboarding status changes.

A Customer can have one the following statuses at a given time:

- `Pending`
- `Approved`
- `Declined`

Once the customer's verification status is `Approved`, any of Noah's transactional flows can be used.

To configure a `Customer` webhook subscription, reference the [Webhook Configuration page](./configuration.md).

## How Webhooks Relate to FiatOptions

When you call the [Hosted Onboarding API](https://docs.noah.com/api-reference/create-onboarding-session), you can specify one or more `FiatOptions` (currencies) that you want the customer to be verified for. **For each `FiatOption` provided, Noah will fire a separate `Pending` webhook** once the customer begins the onboarding process.

The `Verifications` object in each webhook payload indicates which specific `FiatOption` that webhook relates to, allowing you to track the verification status for each currency independently.

## Typical Onboarding Flow

Understanding the webhook firing sequence is crucial for properly implementing your webhook handlers.

Here's what to expect during the customer onboarding flow:

1. **Call the Hosted Onboarding API** with one or more `FiatOptions` (e.g., `["USD", "EUR"]`)
2. **Customer begins onboarding** - Noah immediately fires **one `Pending` webhook per `FiatOption`**
3. **Customer completes the onboarding form** and submits their information
4. **Verification processing occurs** (typically within seconds)
5. **Final webhook(s) arrive** with either:
   - `Approved` status - customer passed automated verification (happy path)
   - `Declined` status - customer failed automated verification
   - Status remains `Pending` - customer requires manual review, or an action from the customer is needed (see [Why a Customer Is Pending](#why-a-customer-is-pending-actionsrequired))

## Timeline Expectations

**Happy Path (Automated Approval):**

- Initial `Pending` webhooks: Fired immediately when customer starts onboarding
- `Approved` webhooks: Typically arrive within **5-15 seconds** after customer submission
- You'll receive one webhook per `FiatOption` for each status change

**Manual Review Scenario:**
When a customer's submission requires manual review (due to unclear documentation, risk factors, or verification ambiguity):

- Initial `Pending` webhooks arrive immediately
- Status remains `Pending` while under manual review
- No additional webhooks are sent until review is complete
- Once review completes, you'll receive `Approved` or `Declined` webhooks

**Customer Action Required Scenario:**
A `Pending` status does not always mean Noah is reviewing the customer — sometimes the customer must do something before verification can continue (e.g., complete an additional questionnaire or re-upload a document). In these cases the `Verifications.ActionsRequired` field lists exactly what is needed. See [Why a Customer Is Pending](#why-a-customer-is-pending-actionsrequired) below.

**Important:** Always implement webhook handling that can process multiple webhooks per customer, especially when using multiple `FiatOptions`.

### Verifications Object Structure

Each webhook contains a `Verifications` object with the overall compliance status and a per-entity breakdown.

The status provided in the main `Verifications` object is the overall compliance status. The main compliance status becomes `Approved` when a user has at least one approved entity. It will be `Declined` if a user is offboarded and is no longer able to transact (non-retryable).

An individual `EntityVerification` status reflects a status for that particular entity. If it's `Declined` there, users can retry verifications for that specific entity, e.g., a blurry document for example; it doesn't impact the other entities that are already approved. If one single entity is approved, the main status would also be `Approved`.

```json
"Verifications": {
  "ActionsRequired": ["ProofOfAddress"],
  "EntityVerifications": [
    {
      "Created": "2025-05-28T20:52:32Z",
      "Entity": "Lt",
      "Model": "Standard",
      "Modified": "2025-05-28T20:52:32Z",
      "Status": "Approved"
    },
    {
      "Created": "2025-05-28T20:53:18Z",
      "Entity": "Us",
      "Model": "Standard",
      "Modified": "2025-05-28T20:53:20Z",
      "Status": "Pending"
    }
  ],
  "Status": "Approved",
  "Type": "Individual"
}
```

**Field Descriptions:**

- `ActionsRequired`: (optional) Array of [actions the customer must take](#why-a-customer-is-pending-actionsrequired) to complete verification. Only present when the customer needs to do something (e.g., complete a questionnaire or re-upload a document); absent when no customer action is needed.
- `Entity`: Noah's geographical jurisdiction, `Us` (USA), `Lt` (EU), or `Ca` (Canada)
- `Model`: Verification method - `Standard` (verified by Noah) or `Reliance` (reliance model)
- `Status`: Current verification status - `Pending`, `Approved`, or `Declined`

### Agreements

Each Customer webhook may include an optional `Agreements` array. The same field is returned on [`GET /customers/{CustomerID}`](https://docs.noah.com/api-reference/customer-by-id); it is **not** included on the paginated [`GET /customers`](https://docs.noah.com/api-reference/customers) list.

`Agreements` lists the terms required for the customer so far, both accepted and still pending. Check `Accepted` on each entry. Each entry uses the `CustomerAgreement` shape:

```json
"Agreements": [
  {
    "Name": "NoahTermsOfService",
    "Version": 1,
    "Accepted": true,
    "AcceptedAt": "2025-05-28T20:52:32Z"
  },
  {
    "Name": "NoahPrivacyPolicy",
    "Version": 1,
    "Accepted": true,
    "AcceptedAt": "2025-05-28T20:52:32Z"
  },
  {
    "Name": "NoahEsignAgreement",
    "Version": 1,
    "Accepted": true,
    "AcceptedAt": "2025-05-28T20:52:32Z"
  }
]
```

**Field Descriptions:**

- `Name`: Identifier of the agreement (`AgreementName`). Possible values:
  - `NoahTermsOfService`
  - `NoahPrivacyPolicy`
  - `NoahEsignAgreement`
  - `LeadBankFundTransfer`
  - `LeadBankTermsAndConditions`
  - `LeadBankBusinessFundTransfer`
- `Version`: Version of the agreement that was presented.
- `Accepted`: Whether the customer has accepted this agreement version.
- `AcceptedAt`: (optional) ISO 8601 timestamp of acceptance. Present only when `Accepted` is `true`.

**How to handle `Agreements`:**

Treat each Customer webhook as a **cumulative snapshot** of the agreements known so far (accepted and pending). Hosted onboarding may first emit pending rows (`Accepted: false`) when a session starts, then update them as the customer accepts. Multi-page flows can emit multiple Customer webhooks; later events include the full set required up to that point. If you miss a webhook, call `GET /customers/{CustomerID}` for the latest agreement state.

## Why a Customer Is Pending: `ActionsRequired`

A customer can remain `Pending` for two distinct reasons:

1. **Noah is working** — the verification is being processed or is under manual review. There is nothing the customer needs to do; wait for the next webhook.
2. **The customer must act** — verification cannot progress until the customer provides additional information or resubmits something.

The `Verifications.ActionsRequired` field distinguishes these cases. When present, it is an array of `VerificationActionRequired` values listing every action currently required from the customer. When the field is absent (or empty), no customer action is needed.

`ActionsRequired` is populated whenever an entity verification is either:

- `Pending` on a step that needs customer input (e.g., an additional questionnaire or a proof-of-address document), or
- `Declined` with a **retryable** rejection (`RejectionData.RejectType` is `Retry`) — the customer can fix the issue and resubmit. Retryable entity-level declines keep the overall status `Pending`; only final (non-retryable) rejections move it to `Declined`.

### Possible Values

| Value                    | Meaning                                                                                                                                               | What the customer should do                                       |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
| `HighRiskInfo`           | The customer has been placed on enhanced due diligence (EDD) and must complete an additional high-risk questionnaire.                                 | Complete the high-risk questionnaire in hosted onboarding.        |
| `HighRiskInfoSenior`     | Same as `HighRiskInfo`, but the senior (60+) variant of the questionnaire applies.                                                                    | Complete the senior high-risk questionnaire in hosted onboarding. |
| `SourceOfWealth`         | The customer must provide source-of-wealth information. Only ever requested for the `Lt` (EU) entity, and always alongside a high-risk questionnaire. | Provide source-of-wealth details in hosted onboarding.            |
| `ProofOfAddress`         | A proof-of-address document is required to complete verification.                                                                                     | Upload a proof-of-address document.                               |
| `DocumentPageMissing`    | A page of the submitted identity document is missing (e.g., only the front side was uploaded).                                                        | Re-upload the identity document including all pages/sides.        |
| `DocumentReupload`       | The submitted identity document was not acceptable — for example unreadable, damaged, expired, invalid, digitally edited, or a screenshot.            | Re-upload a clear photo of a valid, original identity document.   |
| `SelfieReupload`         | The selfie/liveness check failed or did not match the identity document.                                                                              | Redo the selfie/liveness check.                                   |
| `ProofOfAddressReupload` | The previously submitted proof-of-address document was not acceptable.                                                                                | Re-upload a valid proof-of-address document.                      |
| `IncorrectTaxID`         | The provided tax identification number (e.g., SSN) appears to be incorrect.                                                                           | Resubmit the correct tax ID.                                      |

To resolve any of these actions, direct the customer back through [hosted onboarding](https://docs.noah.com/api-reference/create-onboarding-session) — the flow automatically presents the outstanding steps. Once the customer completes them, verification resumes and you will receive further `Customer` webhooks as the status changes.

### Example: Pending with a High-Risk Questionnaire

The customer's EU (`Lt`) verification is pending an enhanced due diligence questionnaire and source-of-wealth information:

```json
"Verifications": {
  "ActionsRequired": ["HighRiskInfo", "SourceOfWealth"],
  "EntityVerifications": [
    {
      "Created": "2025-05-28T20:52:32Z",
      "Entity": "Lt",
      "Model": "Standard",
      "Modified": "2025-05-28T20:55:10Z",
      "Status": "Pending"
    }
  ],
  "Status": "Pending",
  "Type": "Individual"
}
```

### Example: Pending After a Retryable Document Rejection

The customer's document and selfie were rejected but the rejection is retryable (`RejectType: "Retry"`), so the overall status stays `Pending` and the customer can resubmit:

```json
"Verifications": {
  "ActionsRequired": ["DocumentReupload", "SelfieReupload"],
  "EntityVerifications": [
    {
      "Created": "2025-05-28T20:52:32Z",
      "Entity": "Lt",
      "Model": "Standard",
      "Modified": "2025-05-28T21:02:44Z",
      "Status": "Declined",
      "RejectionData": {
        "RejectType": "Retry",
        "PublicComment": "The document photo is unreadable. Please upload a clear photo of your document."
      }
    }
  ],
  "Status": "Pending",
  "Type": "Individual"
}
```

### Example: Pending with a Proof-of-Address Request

The customer must upload a proof-of-address document before verification can complete:

```json
"Verifications": {
  "ActionsRequired": ["ProofOfAddress"],
  "EntityVerifications": [
    {
      "Created": "2025-05-28T20:52:32Z",
      "Entity": "Lt",
      "Model": "Standard",
      "Modified": "2025-05-28T20:58:05Z",
      "Status": "Pending"
    }
  ],
  "Status": "Pending",
  "Type": "Individual"
}
```

:::info
`ActionsRequired` reflects the customer's **current** outstanding actions, recomputed on every webhook. If a `Pending` webhook has no `ActionsRequired`, the customer is under review by Noah and no customer action is needed.
:::

## Event Payload Structure

### Example: Pending Status

When a `Customer` event occurs with status `Pending`, Noah sends a webhook notification to your configured endpoint with the following JSON payload:

```json
{
  "Data": {
    "Created": "2025-05-28T20:52:32Z",
    "CustomerID": "20531",
    "DateOfBirth": "2005-11-29",
    "FullName": {
      "FirstName": "John",
      "LastName": "Mock-Doe"
    },
    "Identities": [
      {
        "ExpiryDate": "2026-05-19",
        "IDNumber": "Mock-CAZQSUG0SL",
        "IDType": "Passport",
        "IssuedDate": "2016-01-17",
        "IssuingCountry": "DE"
      }
    ],
    "PrimaryResidence": {
      "City": "Test",
      "Country": "DE",
      "PostCode": "TT1TT0",
      "State": "Test",
      "Street": "Test Road"
    },
    "Type": "Individual",
    "Verifications": {
      "EntityVerifications": [
        {
          "Created": "2025-05-28T20:52:32Z",
          "Entity": "Lt",
          "Model": "Standard",
          "Modified": "2025-05-28T20:52:32Z",
          "Status": "Pending"
        }
      ],
      "Status": "Pending",
      "Type": "Individual"
    },
    "Metadata": {
      "partnerReference": "usr-8f3a2c"
    }
  },
  "EventType": "Customer",
  "EventVersion": 1748465552988,
  "Occurred": "2025-05-28T20:52:33Z",
  "UserID": "ory|99be1afe-7018-4a7c-be0e-5ab06bdee8c6"
}
```

### Example 2: Approved Status

When the status is `Approved`, Noah sends a webhook notification to your configured endpoint with the following JSON payload:

```json
{
  "Data": {
    "Created": "2025-05-28T20:52:32Z",
    "CustomerID": "20531",
    "DateOfBirth": "2005-11-29",
    "FullName": {
      "FirstName": "John",
      "LastName": "Mock-Doe"
    },
    "Identities": [
      {
        "ExpiryDate": "2026-05-19",
        "IDNumber": "Mock-CAZQSUG0SL",
        "IDType": "Passport",
        "IssuedDate": "2016-01-17",
        "IssuingCountry": "DE"
      }
    ],
    "PrimaryResidence": {
      "City": "Test",
      "Country": "DE",
      "PostCode": "TT1TT0",
      "State": "Test",
      "Street": "Test Road"
    },
    "Type": "Individual",
    "Verifications": {
      "EntityVerifications": [
        {
          "Created": "2025-05-28T20:52:32Z",
          "Entity": "Lt",
          "Model": "Standard",
          "Modified": "2025-05-28T20:52:32Z",
          "Status": "Approved"
        }
      ],
      "Status": "Approved",
      "Type": "Individual"
    },
    "Agreements": [
      {
        "Name": "NoahTermsOfService",
        "Version": 1,
        "Accepted": true,
        "AcceptedAt": "2025-05-28T20:52:32Z"
      },
      {
        "Name": "NoahPrivacyPolicy",
        "Version": 1,
        "Accepted": true,
        "AcceptedAt": "2025-05-28T20:52:32Z"
      },
      {
        "Name": "NoahEsignAgreement",
        "Version": 1,
        "Accepted": true,
        "AcceptedAt": "2025-05-28T20:52:32Z"
      }
    ],
    "Metadata": {
      "partnerReference": "usr-8f3a2c"
    }
  },
  "EventType": "Customer",
  "EventVersion": 1748465552988,
  "Occurred": "2025-05-28T20:52:33Z",
  "UserID": "ory|99be1afe-7018-4a7c-be0e-5ab06bdee8c6"
}
```

### Example 3: Declined Status

When the status is `Declined`, Noah sends a webhook notification to your configured endpoint with the following JSON payload:

```json
{
  "Data": {
    "Created": "2025-05-28T20:52:32Z",
    "CustomerID": "20531",
    "DateOfBirth": "2005-11-29",
    "FullName": {
      "FirstName": "John",
      "LastName": "Mock-Doe"
    },
    "Identities": [
      {
        "ExpiryDate": "2026-05-19",
        "IDNumber": "Mock-CAZQSUG0SL",
        "IDType": "Passport",
        "IssuedDate": "2016-01-17",
        "IssuingCountry": "DE"
      }
    ],
    "PrimaryResidence": {
      "City": "Test",
      "Country": "DE",
      "PostCode": "TT1TT0",
      "State": "Test",
      "Street": "Test Road"
    },
    "Type": "Individual",
    "Verifications": {
      "EntityVerifications": [
        {
          "Created": "2025-05-28T20:52:32Z",
          "Entity": "Lt",
          "Model": "Standard",
          "Modified": "2025-05-28T20:52:32Z",
          "Status": "Declined"
        }
      ],
      "Status": "Declined",
      "Type": "Individual"
    },
    "Metadata": {
      "partnerReference": "usr-8f3a2c"
    }
  },
  "EventType": "Customer",
  "EventVersion": 1748465552988,
  "Occurred": "2025-05-28T20:52:33Z",
  "UserID": "ory|99be1afe-7018-4a7c-be0e-5ab06bdee8c6"
}
```

## FAQ

### Q: Why does the `CustomerID` format appear to change between webhooks?

**A:** It doesn't. The `CustomerID` in the webhook is the exact ID you provided when creating the hosted onboarding session. Noah does not modify this value. If you're seeing different formats, check whether you're providing consistent IDs when initiating onboarding sessions.

### Q: Can I get the customer's email address in the webhook?

**A:** No. The customer's email address is not included in webhook payloads for privacy reasons. Since you control the onboarding flow, we recommend that you:

1. Collect the email address in your application before redirecting to Noah's hosted onboarding.
2. Associate it with the `CustomerID` in your system.

:::tip
You can use the [pre-fill feature](../../../api-reference/prefill-customer-details) in the hosted onboarding session to pass known customer data (including email) to the onboarding form.
:::
