# Sync KYC / Onboarding Status

Pull the user's latest onboarding and KYC status from the bank ramp and store it — useful right after the user returns from the hosted onboarding.

## Overview

Aurea normally learns KYC results from the bank ramp's `Customer` webhook. When the user has just come back from the [onboarding session](https://docs.aureahub.com/docs/payin-session.md) and you don't want to wait for it, call this endpoint: Aurea asks the bank ramp for the customer, updates its record if anything changed, and returns the result.

- Send `{ "isTestnet": true }` as the JSON body to sync the user's sandbox profile with the bank ramp's sandbox; without it Aurea syncs the production profile. The `?isTestnet=true` query parameter is still read when the body has no `isTestnet`; when both are sent, the body wins.
- Aurea stores the bank ramp's answer by the same rules as the bank ramp's `Customer` event: the statuses never move back (an approved user stays approved unless the bank ramp declines them, even when the bank ramp's answer says nothing about the verification), and each region's verification, what the bank ramp asks the user to do and the agreements are kept and returned in `verification`, described on [Onboarding Status](https://docs.aureahub.com/docs/payin-status.md). If a bank ramp event changed the user while Aurea was asking the bank ramp, the actions and agreements that event brought are kept.
- `synced` is `true` only when `kycStatus` or `onboardingStatus` changed. `false` means they were already up to date — or that the user isn't known to the bank ramp yet, in which case Aurea returns what it has stored.
- A user who never started onboarding in that environment gets `not_started` statuses, and the bank ramp isn't called.

## Endpoint

### `POST /v1/ramp/bank/payin/sync-status`

Authentication: bearer token required.

Fetches the status of the user's profile in one environment from the bank ramp, stores it, and returns the up-to-date onboarding and KYC status.

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `isTestnet` | boolean | no | Read only when the body has no `isTestnet`; same meaning |

**Request body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `isTestnet` | boolean | no | `true` = the sandbox profile and the bank ramp's sandbox; `false` or omitted = production |

**Responses**

`200` OK

```json
{
  "customerId": "3f6c2a8e-5b1d-4e7a-9c3f-2d8e1b4a6c90",
  "noahCustomerId": "aurea_5d1f0c9b2e7a4c3d8f6b1a0e9c2d7b4f5a3e",
  "onboardingStatus": "completed",
  "kycStatus": "approved",
  "canInitiateDeposit": true,
  "synced": true,
  "message": "Status synced from the bank ramp. Onboarding: completed, KYC: approved.",
  "verification": {
    "status": "Approved",
    "customerType": "Individual",
    "regions": [
      { "entity": "Lt", "region": "EU", "status": "Approved", "rejection": null }
    ],
    "actionsRequired": [],
    "agreements": [
      { "name": "NoahTermsOfService", "version": 1, "accepted": true, "acceptedAt": "2026-09-17T09:12:41Z" },
      { "name": "NoahPrivacyPolicy", "version": 1, "accepted": true, "acceptedAt": "2026-09-17T09:12:41Z" }
    ],
    "nextStep": "approved"
  }
}
```

`503` Bank Ramp not configured

```json
{
  "statusCode": 503,
  "error": "NoahApiError",
  "message": "The bank ramp is not configured for this tenant in production",
  "details": { "code": "NOAH_NOT_CONFIGURED" }
}
```

When nothing changed the message is `Status already up to date. Onboarding: …, KYC: ….` and `synced` is `false`.

`verification.nextStep` answers `start_onboarding` for a user who never opened the link *and* for one the bank ramp turned away on its page — the bank ramp has no customer in either case. Straight after a return, read it as the second: do not open a new session automatically, or the user walks into the same wall for ever ([When the Bank Ramp Says No](https://docs.aureahub.com/docs/payin-session.md)).

## Implementation

```typescript
// Call when the user lands on your returnUrl after the hosted onboarding
async function refreshKycAfterReturn(token: string, { sandbox = false, attempts = 5 } = {}) {
  const url = 'https://api.aureahub.com/v1/ramp/bank/payin/sync-status';

  for (let i = 0; i < attempts; i++) {
    const res = await fetch(url, {
      method: 'POST',
      headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
      body: JSON.stringify({ isTestnet: sandbox }) // the same value used for the onboarding session
    });
    if (!res.ok) throw new Error(`Sync failed: ${res.status}`);

    const status = await res.json();
    // approved, rejected, or something the user must do in a new onboarding session
    if (status.verification.nextStep !== 'wait_for_review') return status;

    await new Promise(r => setTimeout(r, 3_000)); // automated checks usually finish within seconds
  }
  return null; // still under review — the bank ramp's Customer webhook will update Aurea later
}
```

---

Web version: https://docs.aureahub.com/#payin-sync-status
