> ## Documentation Index
> Fetch the complete documentation index at: https://docs.privy.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Track KYC status

Verification is asynchronous. Bridge reviews the submission after the user completes the hosted flow, and can revoke an endorsement later. Your app can poll for status or subscribe to webhooks. See Bridge's [customer status lifecycle](https://apidocs.bridge.xyz/platform/customers/customers/api) for how Bridge derives each status.

Before tracking status, [create a KYC link for the user](/kyc-kyb/kyc).

## Fetch by API

<View title="REST API" icon="terminal">
  To read a user's current KYC status, make a `GET` request to:

  ```bash theme={"system"}
  https://api.privy.io/v1/users/{user_id}/kyc
  ```

  The endpoint accepts the following query parameter:

  <ParamField query="provider" type="'bridge'">
    Only return status for this provider. If omitted, the response includes every provider the user
    has been verified with.
  </ParamField>

  Below is a sample cURL command for this request:

  ```bash theme={"system"}
  curl --request GET 'https://api.privy.io/v1/users/did:privy:xxxxx/kyc?provider=bridge' \
    -u "<your-privy-app-id>:<your-privy-app-secret>" \
    -H "privy-app-id: <your-privy-app-id>"
  ```

  The response is an array with one entry per provider. Privy fetches status from Bridge on every
  request, so revoked endorsements are reflected immediately.

  <ResponseField name="provider" type="'bridge'">
    Provider that verified the user.
  </ResponseField>

  <ResponseField name="environment" type="'production' | 'sandbox'">
    Bridge environment the customer belongs to.
  </ResponseField>

  <ResponseField name="status" type="string">
    Top-level status of the customer, as reported by Bridge. A customer is `active` once any
    endorsement is approved.
  </ResponseField>

  <ResponseField name="tos" type="object">
    Terms of service state.

    <Expandable title="properties" defaultOpen>
      <ResponseField name="tos.status" type="string">
        Status of terms of service acceptance.
      </ResponseField>

      <ResponseField name="tos.link" type="string">
        URL to accept the terms of service. Present while acceptance is pending.
      </ResponseField>
    </Expandable>
  </ResponseField>

  <ResponseField name="kyc" type="object">
    Verification state.

    <Expandable title="properties" defaultOpen>
      <ResponseField name="kyc.status" type="string">
        Status of the user's verification.
      </ResponseField>

      <ResponseField name="kyc.link" type="string">
        URL to complete verification. Present while verification is outstanding.
      </ResponseField>

      <ResponseField name="kyc.rejection_reasons" type="string[]">
        [Reasons](https://apidocs.bridge.xyz/platform/customers/customers/rejection_reasons) Bridge
        rejected the submission.
      </ResponseField>
    </Expandable>
  </ResponseField>

  <ResponseField name="endorsements" type="object[]">
    Endorsements requested for the user.

    <Expandable title="properties" defaultOpen>
      <ResponseField name="endorsements[].name" type="string">
        Name of the
        [endorsement](https://apidocs.bridge.xyz/platform/customers/customers/endorsements), such as
        `base` or `sepa`.
      </ResponseField>

      <ResponseField name="endorsements[].status" type="string">
        Status of the endorsement, as reported by Bridge.
      </ResponseField>

      <ResponseField name="endorsements[].missing" type="string[] | null">
        Requirements Bridge is still waiting on. `null` when the endorsement is complete.
      </ResponseField>
    </Expandable>
  </ResponseField>

  <ResponseField name="capabilities" type="object">
    Status of each capability the customer can use.

    <Expandable title="properties" defaultOpen>
      <ResponseField name="capabilities.payin_crypto" type="string">
        Whether the customer can deposit crypto.
      </ResponseField>

      <ResponseField name="capabilities.payout_crypto" type="string">
        Whether the customer can receive crypto payouts.
      </ResponseField>

      <ResponseField name="capabilities.payin_fiat" type="string">
        Whether the customer can deposit fiat.
      </ResponseField>

      <ResponseField name="capabilities.payout_fiat" type="string">
        Whether the customer can receive fiat payouts.
      </ResponseField>
    </Expandable>
  </ResponseField>

  <ResponseField name="requirements_due" type="string[]">
    Requirements the user must still satisfy, such as linking a bank account.
  </ResponseField>

  <ResponseField name="future_requirements_due" type="string[]">
    Requirements the user will need to satisfy in the future.
  </ResponseField>

  ```json theme={"system"}
  [
    {
      "provider": "bridge",
      "environment": "sandbox",
      "status": "active",
      "tos": {
        "status": "approved"
      },
      "kyc": {
        "status": "approved",
        "rejection_reasons": []
      },
      "endorsements": [
        {
          "name": "base",
          "status": "approved",
          "missing": null
        }
      ],
      "capabilities": {
        "payin_crypto": "active",
        "payout_crypto": "active",
        "payin_fiat": "active",
        "payout_fiat": "active"
      },
      "requirements_due": [],
      "future_requirements_due": []
    }
  ]
  ```
</View>

## Webhooks

Privy emits a `user.kyc.updated` [webhook](/api-reference/webhooks/overview) whenever a user's verification state changes at Bridge, re-emitting Bridge's [customer webhooks](https://apidocs.bridge.xyz/platform/additional-information/webhooks/structure) as a Privy event. The payload carries a full state snapshot in `data` and a `changes` diff of the fields that moved, so your app can react to a specific transition without storing prior state.

<Info>
  Webhooks can be tested at no cost in development environments. To enable webhooks in production,
  upgrade to the Enterprise plan in the Privy Dashboard.
</Info>

<Expandable title="user.kyc.updated attributes" defaultOpen="true">
  <ResponseField name="type" type="'user.kyc.updated'" required>
    Type of the webhook event.
  </ResponseField>

  <ResponseField name="user_id" type="string" required>
    ID of the Privy user whose verification state changed.
  </ResponseField>

  <ResponseField name="provider" type="'bridge'" required>
    Provider that reported the change.
  </ResponseField>

  <ResponseField name="environment" type="'production' | 'sandbox'" required>
    Bridge environment the customer belongs to.
  </ResponseField>

  <ResponseField name="data" type="object" required>
    Full snapshot of the user's verification state at the time of the event.

    <Expandable title="properties" defaultOpen>
      <ResponseField name="data.status" type="string" required>
        Top-level status of the customer, as reported by Bridge.
      </ResponseField>

      <ResponseField name="data.tos" type="object" required>
        Terms of service state, containing `status`.
      </ResponseField>

      <ResponseField name="data.kyc" type="object" required>
        Verification state, containing `status`.
      </ResponseField>

      <ResponseField name="data.endorsements" type="object[]" required>
        Endorsements for the user. Each entry contains `name`, `status`, and `missing`, which is
        `null` when the endorsement is complete.
      </ResponseField>

      <ResponseField name="data.capabilities" type="object" required>
        Status of `payin_crypto`, `payout_crypto`, `payin_fiat`, and `payout_fiat`.
      </ResponseField>
    </Expandable>
  </ResponseField>

  <ResponseField name="changes" type="object" required>
    Fields that changed, keyed by dot-notation path. Each value is a `[previous, current]` tuple.
    Privy omits events where no meaningful verification field changed.
  </ResponseField>
</Expandable>

<Accordion title="Example: base endorsement approved">
  ```json theme={"system"}
  {
    "type": "user.kyc.updated",
    "user_id": "did:privy:xxxxx",
    "provider": "bridge",
    "environment": "sandbox",
    "data": {
      "status": "active",
      "tos": {
        "status": "approved"
      },
      "kyc": {
        "status": "active"
      },
      "endorsements": [
        {
          "name": "base",
          "status": "approved",
          "missing": null
        }
      ],
      "capabilities": {
        "payin_crypto": "active",
        "payout_crypto": "active",
        "payin_fiat": "pending",
        "payout_fiat": "pending"
      }
    },
    "changes": {
      "endorsements.base.status": ["incomplete", "approved"],
      "capabilities.payin_crypto": ["pending", "active"]
    }
  }
  ```
</Accordion>

If an endorsement is incomplete, read `data.endorsements[].missing` for the requirements Bridge is still waiting on, then create a new KYC link for the user to resolve them.

## Next steps

<CardGroup cols={2}>
  <Card title="Handling webhook events" icon="webhook" href="/user-management/users/webhooks/handling-events">
    Configure an endpoint to receive Privy webhook events
  </Card>

  <Card title="KYB" icon="building" href="/kyc-kyb/kyb">
    Verify an organization
  </Card>
</CardGroup>
