# Migrating to API v3

If you've integrated with GrailPay over the past few years, you've probably noticed that our API grew in layers. Version 1 came first. Version 2 reshaped parts of it but left others in place, so a typical "v2 integration" still reaches back to v1 to create a transaction or register a webhook. Some endpoints live under `3p/`, others under `processor/`. It works, but it asks you to know more about our history than you should have to.

API v3 is the version we'd have built from the start if we'd known then what we know now. It's one API, under one base path, with a consistent shape for every request and response. This guide walks through what changed, when the earlier versions stop working, and how each endpoint and webhook event you use today maps to its v3 replacement. All three versions of the API reference are on this site — [v3](/api/payments/), and [v2](/api/payments/v2/) and [v1](/api/payments/v1/) marked as deprecated — so you can compare an old endpoint and its replacement side by side.

:::caution[Deprecation and sunset dates]
- **April 1, 2027 — v1 and v2 are deprecated.** Both keep working, but we won't be making changes to them.
- **October 1, 2027 — v1 and v2 are sunset.** From this date, v1 and v2 endpoints stop responding and legacy webhook events are no longer delivered.

Every integration needs to be on v3 before October 1, 2027.
:::

## Timeline

| Date | Milestone | What it means for you |
| --- | --- | --- |
| Now | v3 available | Build anything new on v3, and start planning the move for what you already have. |
| April 1, 2027 | v1 and v2 deprecated | Your existing calls keep working. No new features, fixes, or changes land on these versions. |
| October 1, 2027 | v1 and v2 sunset | v1 and v2 endpoints stop responding. Legacy webhook events stop being delivered. |

There's a full year between now and the sunset date, and six months of formal deprecation before that. We set it up that way so nobody has to rush, but we'd encourage you to start early: the entity model changed in ways that are easier to absorb when you're not working against a deadline.

## What changed

**You no longer need to know our internal user UUIDs.** This is the change most integrators will feel first. In v1 and v2, creating a transaction for a business or merchant meant knowing the user UUID we'd assigned behind that entity, which was never a concept you should have had to carry. In v3, you hand us any entity UUID — a person, a business, a merchant — and we work out the rest. The same is true everywhere an entity is referenced: the UUID you already have is the one you use.

**Requests and responses are consistent.** Every v3 endpoint returns the same entity in the same shape. The transaction object you get back from `POST /api/v3/transactions` is the one you get from `GET /api/v3/transactions/{uuid}`, and it's the one that arrives inside a `transaction.completed` webhook. In v1 and v2, each endpoint and each event had its own field set, and reconciling them was your problem. Field names are consistent across entities too: `timestamps`, `bank_identifiers`, `modality`, and the like mean the same thing wherever they appear.

**One API, one base path.** Everything lives under `/api/v3/`. There's no `3p/` and `processor/` split; the caller's role comes from authentication, not the URL. A v3 integration doesn't need any v1 or v2 calls alongside it.

**People, businesses, and merchants are separate resources.** v2 addressed all three through a single `users` resource, which meant the shape of a response depended on what you'd happened to fetch. v3 gives each its own resource under `/people`, `/businesses`, and `/merchants`, so onboarding, lookups, and updates are explicit about what they're acting on.

**Bank accounts stand on their own.** A bank account is no longer nested under a user and addressed by aggregator type. Each one has a UUID and lives at `/api/v3/bank-accounts/{uuid}`, whichever entity it belongs to and however it was linked.

**Payouts are one resource, including processor payouts.** The v2 batch payout endpoints, split four ways by entity type with a separate set for processors, are replaced by a single `/api/v3/payouts` resource. Processor payouts are part of it: filter the list endpoint to see them, or fetch one directly by UUID. Lifecycle events for them still arrive as `processor_payout.*` webhooks.

**Billing is split in two.** The single v2 billing endpoint becomes `/api/v3/billing/summary`, which gives you totals grouped by billable event, and `/api/v3/billing/items`, which gives you the individual events behind those totals.

**Webhook events carry an envelope.** Every v3 event shares the same top-level fields — `event`, `event_uuid`, `event_occurred_at`, `event_version`, `vendor_uuid`, and `data` — and event names are namespaced as `group.action` (`transaction.created`) rather than PascalCase (`TransactionStarted`). The entity inside `data` is the same object the API returns. The [Webhook event changes](#webhook-event-changes) section below goes into detail.

**Webhooks are managed in the API, and subscription is simpler.** Registering, listing, and removing webhooks happens through `/api/v3/webhooks`, replacing the v1 `webhook` endpoints and their processor-side twins. The subscription model changed as well: in v1 and v2 you subscribed to each event individually and received only those. In v3 you register one URL and receive every event. Your handler decides which ones to act on. Verification changes too: legacy deliveries carried `X-Caller-Auth`, a hash of your API key, while a v3 delivery carries a `Signature` header, an HMAC-SHA256 of the body keyed with the secret returned when you registered the webhook.

## Endpoint changes: v2 to v3

Every v2 path starts with `/3p/api/v2/`; every v3 path starts with `/api/v3/`. Beyond that, the tables below show where each endpoint went. Where v3 has something v2 didn't, it's listed with a dash in the v2 column so you can see what's newly available to you.

A reminder before you start: if your integration is "on v2," check it against the [v1 tables](#endpoint-changes-v1-to-v3) too. v2 never had endpoints for creating transactions or managing webhooks, so those calls are almost certainly v1.

### Bank accounts

| v2 | v3 | Notes |
| --- | --- | --- |
| `POST /3p/api/v2/bank-accounts/validate` | `POST /api/v3/bank-accounts/validate` | |
| `GET /3p/api/v2/users/{uuid}/bank-accounts` | `GET /api/v3/bank-accounts` | Filter by the owning entity. **Confirm** filter parameter. |
| `POST /3p/api/v2/users/{uuid}/bank-accounts` | `POST /api/v3/bank-accounts` or `POST /api/v3/people/{uuid}/bank-accounts` | The entity is identified in the request rather than the path. **Confirm** which to use for businesses and merchants. |
| `GET /3p/api/v2/users/{uuid}/bank-accounts/default` | `GET /api/v3/bank-accounts` | No dedicated endpoint; the default account is marked `is_default` in the list. **Confirm.** |
| `DELETE /3p/api/v2/bank-accounts/{aggregator_type}/{account_uuid}` | `DELETE /api/v3/bank-accounts/{uuid}` | Aggregator type is no longer part of the path. |
| `GET /3p/api/v2/users/{uuid}/bank-accounts/{account_uuid}/balance` | `GET /api/v3/bank-accounts/{uuid}/balance` | |
| `GET /3p/api/v2/bank-accounts/{aggregator_type}/{account_uuid}/history` | `GET /api/v3/bank-accounts/{uuid}/history` | |
| — | `PUT /api/v3/bank-accounts/{uuid}/default` | New in v3, restoring the v1 switch-default capability that v2 dropped. |
| — | `GET /api/v3/bank-accounts/{uuid}/owners` | New in v3. |

### Billing

| v2 | v3 | Notes |
| --- | --- | --- |
| `GET /3p/api/v2/merchants/{uuid}/billing` | `GET /api/v3/billing/summary` | Totals grouped by billable event. The merchant is a query parameter rather than a path segment. **Confirm** parameter name. |
| — | `GET /api/v3/billing/items` | New in v3: the individual billing events behind the summary. |

### Payouts

| v2 | v3 | Notes |
| --- | --- | --- |
| `GET /3p/api/v2/batch-payouts` | `GET /api/v3/payouts` | One list for all payouts. **Confirm** how a v2 batch maps to v3 payouts. |
| `GET /3p/api/v2/batch-payouts/{batch_payout_uuid}` | `GET /api/v3/payouts/{uuid}` | **Confirm.** |
| `GET /3p/api/v2/batch-payouts/business/{business_user_uuid}` | `GET /api/v3/payouts` | Filter by payee. **Confirm** filter parameter. |
| `GET /3p/api/v2/batch-payouts/business/{business_user_uuid}/{batch_payout_uuid}` | `GET /api/v3/payouts/{uuid}` | **Confirm.** |
| `GET /3p/api/v2/batch-payouts/merchant/{merchant_user_uuid}` | `GET /api/v3/payouts` | Filter by payee. **Confirm.** |
| `GET /3p/api/v2/batch-payouts/merchant/{merchant_user_uuid}/{batch_payout_uuid}` | `GET /api/v3/payouts/{uuid}` | **Confirm.** |
| `GET /3p/api/v2/batch-payouts/person/{user_uuid}` | `GET /api/v3/payouts` | Filter by payee. **Confirm.** |
| `GET /3p/api/v2/batch-payouts/person/{user_uuid}/{batch_payout_uuid}` | `GET /api/v3/payouts/{uuid}` | **Confirm.** |
| `GET /3p/api/v2/batch-payouts/processor` | `GET /api/v3/payouts` | Filter for processor payouts. **Confirm** filter parameter. |
| `GET /3p/api/v2/batch-payouts/processor/{batch_payout_uuid}` | `GET /api/v3/payouts/{uuid}` | |
| `GET /3p/api/v2/batch-merchant-payouts/{batch_merchant_payout_uuid}` | `GET /api/v3/payouts/{uuid}` | **Confirm.** |
| — | `POST /api/v3/payouts/standalone` | New in v3: create a payout that isn't tied to a transaction. |

### Refunds

| v2 | v3 | Notes |
| --- | --- | --- |
| `GET /3p/api/v2/refunds` | `GET /api/v3/refunds` | |
| `GET /3p/api/v2/refunds/{uuid}` | `GET /api/v3/refunds/{uuid}` | |
| `GET /3p/api/v2/batch-refunds` | `GET /api/v3/refunds` | Batch refund detail is folded into the refund resource. |
| `GET /3p/api/v2/batch-refunds/{batch_refund_uuid}` | `GET /api/v3/refunds/{uuid}` | As above. |
| — | `POST /api/v3/refunds` | v2 had no refund creation endpoint; v2 integrations used `POST /3p/api/v1/transactions/{uuid}/refund`. |

### Transactions

| v2 | v3 | Notes |
| --- | --- | --- |
| `GET /3p/api/v2/transactions` | `GET /api/v3/transactions` | |
| `GET /3p/api/v2/transactions/{uuid}` | `GET /api/v3/transactions/{uuid}` | |
| `DELETE /3p/api/v2/transactions/{uuid}` | `DELETE /api/v3/transactions/{uuid}/cancel` | |
| — | `POST /api/v3/transactions` | v2 had no create endpoint; v2 integrations used `POST /3p/api/v1/transaction`. Any entity UUID identifies the payor and payee. |
| — | `POST /api/v3/transactions/{uuid}/pause` | New in v3. |
| — | `POST /api/v3/transactions/{uuid}/resume` | New in v3. |

### Users, people, businesses, and merchants

| v2 | v3 | Notes |
| --- | --- | --- |
| `POST /3p/api/v2/businesses` | `POST /api/v3/businesses` or `POST /api/v3/merchants` | One v2 endpoint onboarded both; v3 separates them. Use `/merchants` for entities that undergo KYB. |
| `PUT /3p/api/v2/users/{uuid}/kyb` | `PATCH /api/v3/merchants/{uuid}` | KYB details are updated on the merchant. **Confirm.** |
| `GET /3p/api/v2/users` | `GET /api/v3/people`, `GET /api/v3/businesses`, `GET /api/v3/merchants` | Split by entity type. |
| `POST /3p/api/v2/users` | `POST /api/v3/people` | |
| `GET /3p/api/v2/users/{uuid}` | `GET /api/v3/people/{uuid}`, `GET /api/v3/businesses/{uuid}`, `GET /api/v3/merchants/{uuid}` | Split by entity type. |
| `DELETE /3p/api/v2/users/{uuid}` | `DELETE /api/v3/people/{uuid}` | For merchants, use `POST /api/v3/merchants/{uuid}/deactivate`. Businesses: **Confirm.** |
| — | `PATCH /api/v3/people/{uuid}`, `PATCH /api/v3/businesses/{uuid}` | New in v3. |
| — | `POST /api/v3/merchants/{uuid}/activate`, `POST /api/v3/merchants/{uuid}/deactivate` | New in v3. |
| — | `POST /api/v3/people/kyc` | New in v3: standalone KYC verification for a person. |

### Webhooks

| v2 | v3 | Notes |
| --- | --- | --- |
| `GET /3p/api/v2/webhook-events` | — | No v3 equivalent; the event catalog is documented at [Webhook Events](/docs/technical/webhooks/events/). **Confirm.** |
| — | `GET /api/v3/webhooks`, `POST /api/v3/webhooks`, `GET /api/v3/webhooks/{uuid}`, `DELETE /api/v3/webhooks/{uuid}` | v2 had no webhook management; v2 integrations used the v1 `webhook` endpoints (see below). |

### New in v3 with no v2 counterpart

| v3 | Purpose |
| --- | --- |
| `GET /api/v3/me` | Fetch the authenticated account. |
| `GET /api/v3/clawbacks`, `GET /api/v3/clawbacks/{uuid}` | Read clawbacks. |
| `GET /api/v3/returns` | Read ACH returns. |
| `GET /api/v3/reverse-payouts`, `GET /api/v3/reverse-payouts/{uuid}` | Read reverse payouts. |
| `GET /api/v3/vendors/{uuid}` | Fetch vendor details. |
| `GET /api/v3/vendors/{uuid}/fbo`, `POST /api/v3/vendors/{uuid}/fbo/funding`, `DELETE /api/v3/vendors/{uuid}/fbo/funding/{funding_uuid}` | Prefunded FBO account and funding. |
| `GET /api/v3/vendors/{uuid}/bank-accounts`, `POST …/bank-accounts`, `PUT …/bank-accounts/{bank_account_uuid}/default`, `DELETE …/bank-accounts/{bank_account_uuid}` | Vendor funding bank accounts. |

## Endpoint changes: v1 to v3

v1 had two parallel surfaces: `/3p/api/v1/` for vendors and `/processor/api/v1/` for processors, with many endpoints duplicated across them. v3 has one surface, and who you are is settled by how you authenticate rather than which prefix you call. In the tables below, both v1 surfaces map onto the same v3 endpoints.

### Bank accounts

| v1 | v3 | Notes |
| --- | --- | --- |
| `GET /3p/api/v1/bank-account/{user_uuid}` | `GET /api/v3/bank-accounts/{uuid}` | Addressed by bank account UUID, not user. |
| `POST /3p/api/v1/bank-account/{user_uuid}` | `POST /api/v3/bank-accounts` or `POST /api/v3/people/{uuid}/bank-accounts` | **Confirm** which to use for businesses and merchants. |
| `GET /3p/api/v1/bank-account/list/{user_uuid}` | `GET /api/v3/bank-accounts` | Filter by entity. **Confirm** parameter. |
| `GET /3p/api/v1/bank-account/balance/{user_uuid}` | `GET /api/v3/bank-accounts/{uuid}/balance` | |
| `PUT /3p/api/v1/bank-account/switch/default/{user_uuid}` | `PUT /api/v3/bank-accounts/{uuid}/default` | |
| `GET /3p/api/v1/cashflow/prediction/{user_uuid}` | — | Not in the v3 Payments API. **Confirm** whether this moved to the Risk Intelligence API. |
| `POST /3p/api/v1/bank-account/user` | — | No v3 equivalent. **Confirm.** |
| `GET /processor/api/v1/bank-account/{user_uuid}` | `GET /api/v3/bank-accounts/{uuid}` | |
| `GET /processor/api/v1/bank-account/list/{user_uuid}` | `GET /api/v3/bank-accounts` | |
| `GET /processor/api/v1/bank-account/balance/{user_uuid}` | `GET /api/v3/bank-accounts/{uuid}/balance` | |
| `GET /processor/api/v1/cashflow-prediction/{user_uuid}` | — | As above. **Confirm.** |

### Payouts

| v1 | v3 | Notes |
| --- | --- | --- |
| `GET /3p/api/v1/batchpayouts` | `GET /api/v3/payouts` | **Confirm** how a v1 batch maps to v3 payouts. |
| `GET /3p/api/v1/batchpayout/{payout_uuid}` | `GET /api/v3/payouts/{uuid}` | **Confirm.** |
| `GET /processor/api/v1/batchpayout/{payout_uuid}` | `GET /api/v3/payouts/{uuid}` | Processor payouts are fetched by UUID like any other payout. |

### Refunds

| v1 | v3 | Notes |
| --- | --- | --- |
| `POST /3p/api/v1/transactions/{uuid}/refund` | `POST /api/v3/refunds` | The transaction is identified in the request body. |
| `GET /3p/api/v1/transactions/{transaction_uuid}/refunds` | `GET /api/v3/refunds` | Filter by transaction. **Confirm** parameter. |
| `GET /3p/api/v1/refunds/{refund_uuid}` | `GET /api/v3/refunds/{uuid}` | |
| `GET /3p/api/v1/batchrefunds` | `GET /api/v3/refunds` | Batch refund detail is folded into the refund resource. |
| `GET /3p/api/v1/batchrefunds/{batch_refund_uuid}` | `GET /api/v3/refunds/{uuid}` | As above. |

### Transactions

| v1 | v3 | Notes |
| --- | --- | --- |
| `POST /3p/api/v1/transaction` | `POST /api/v3/transactions` | Any entity UUID identifies the payor and payee; no user UUID lookup needed. |
| `GET /3p/api/v1/transaction/{uuid}` | `GET /api/v3/transactions/{uuid}` | |
| `GET /3p/api/v1/transaction/list` | `GET /api/v3/transactions` | |
| `DELETE /3p/api/v1/transaction/{uuid}` | `DELETE /api/v3/transactions/{uuid}/cancel` | |

### Users, people, businesses, and merchants

| v1 | v3 | Notes |
| --- | --- | --- |
| `POST /3p/api/v1/register/business` | `POST /api/v3/businesses` or `POST /api/v3/merchants` | Use `/merchants` for entities that undergo KYB. |
| `POST /3p/api/v1/register/person` | `POST /api/v3/people` | |
| `POST /3p/api/v1/user/kyb/{uuid}` | `PATCH /api/v3/merchants/{uuid}` | **Confirm.** |
| `GET /3p/api/v1/user/{uuid}` | `GET /api/v3/people/{uuid}`, `GET /api/v3/businesses/{uuid}`, `GET /api/v3/merchants/{uuid}` | Split by entity type. |
| `GET /3p/api/v1/users` | `GET /api/v3/people`, `GET /api/v3/businesses`, `GET /api/v3/merchants` | Split by entity type. |
| `DELETE /3p/api/v1/users/{uuid}` | `DELETE /api/v3/people/{uuid}` | For merchants, use `POST /api/v3/merchants/{uuid}/deactivate`. Businesses: **Confirm.** |

### Webhooks

| v1 | v3 | Notes |
| --- | --- | --- |
| `GET /3p/api/v1/webhook` | `GET /api/v3/webhooks` | |
| `POST /3p/api/v1/webhook` | `POST /api/v3/webhooks` | |
| `DELETE /3p/api/v1/webhook` | `DELETE /api/v3/webhooks/{uuid}` | Addressed by webhook UUID. |
| `GET /processor/api/v1/webhook` | `GET /api/v3/webhooks` | One endpoint for all callers. |
| `POST /processor/api/v1/webhook` | `POST /api/v3/webhooks` | |
| `DELETE /processor/api/v1/webhook` | `DELETE /api/v3/webhooks/{uuid}` | |

## Webhook event changes

Legacy webhook events stop being delivered on October 1, 2027, the same day the v1 and v2 endpoints go dark. Until then, a webhook registered through v1 keeps receiving the legacy events it subscribed to, and a webhook registered through `/api/v3/webhooks` receives every v3 event. There is no crossover: a v1 registration never receives v3 events, and a v3 registration never receives legacy events.

If you've only ever consumed legacy events, the v3 shape will feel different at first, but it's different in a way that makes your handler simpler.

### The envelope

A legacy payload put every field at the top level, named the event in PascalCase, and gave you no way to tell a retried delivery from a new one. A v3 payload wraps the entity under `data` and surrounds it with a small, predictable envelope:

```json title="Legacy"
{
  "event": "TransactionStarted",
  "uuid": "b5c337d8-d886-11ed-afa1-0242ac120002",
  "status": "CAPTURE_PENDING",
  "amount": 12000
}
```

```json title="v3"
{
  "event": "transaction.created",
  "event_uuid": "019d072d-1203-7695-842b-a15567fbb0d8",
  "event_occurred_at": "2026-04-05 14:28:33",
  "event_version": 3,
  "vendor_uuid": "019e2711-5a92-735d-a7d5-669aec76b54f",
  "data": {
    "transaction": { }
  }
}
```

Four things follow from this:

- **You get every event.** A v3 registration receives all events, not a subscribed subset. Filter on `event` in your handler rather than at registration time.
- **Route on `event`, de-duplicate on `event_uuid`.** Every delivery, including retries, carries the same `event_uuid`, so an idempotent handler is a one-line check.
- **`vendor_uuid` is always there.** Legacy events included it only on processor-bound payloads. In v3 it's part of every envelope.
- **The entity inside `data` is the API's entity.** The `transaction` object in `transaction.created` has the same shape as the one in `transaction.completed`, and the same shape `GET /api/v3/transactions/{uuid}` returns. Legacy events each had their own field set; v3 events don't.

Full payloads for every event are on the [Webhook Events](/docs/technical/webhooks/events/) pages.

### Event mapping

Most legacy events have a direct v3 replacement with a new name. A few were split, a few were removed because the concept they described is gone, and v3 adds events for things the legacy set never covered.

| Legacy event | v3 event | Notes |
| --- | --- | --- |
| `BankAccountRemoved` | `bank_account.removed` | |
| `BankLinkFailed` | `bank_account.link_failed` | |
| `BankLinkedSuccessfully` | `bank_account.link_success` | |
| `BatchPayoutBusiness` | — | Removed. Payouts are reported individually through `payout.*` events. |
| `BatchPayoutMerchant` | — | Removed. As above. |
| `BatchPayoutPerson` | — | Removed. As above. |
| `BatchPayout` | — | Removed. Processor payouts are reported through `processor_payout.*` events. |
| `BatchRefund` | `batch_refund.created` | |
| `BusinessCreated` | `business.created` | |
| `BusinessUpdated` | `business.updated` | |
| `ClawbackStarted` | `clawback.created` | **Confirm.** |
| `ClawbackCaptureStarted` | `clawback.processing` | **Confirm.** |
| `ClawbackFailed` | `clawback.failed` | |
| `ClawbackCompleted` | `clawback.completed` | |
| `ComplianceStatusChanged` | `compliance_status.processing`, `compliance_status.in_review`, `compliance_status.approved`, `compliance_status.rejected` | One event per status instead of one event with a status field. |
| `MerchantCreated` | `merchant.created` | |
| `MerchantUpdated` | `merchant.updated` | |
| `NocReceived` | `noc.received` | |
| `NocProcessed` | `noc.processed` | |
| `PayoutCreated` | `payout.created` | |
| — | `payout.processing` | New in v3. |
| `PayoutFailed` | `payout.failed` | |
| `PayoutCompleted` | `payout.completed` | |
| `PersonCreated` | `person.created` | |
| `PersonUpdated` | `person.updated` | |
| `PersonStatusChanged` | `person.status_changed` | |
| `ProcessorPayoutCreated` | `processor_payout.created` | |
| — | `processor_payout.processing` | New in v3. |
| `ProcessorPayoutFailed` | `processor_payout.failed` | |
| `ProcessorPayoutCompleted` | `processor_payout.completed` | |
| `RefundPending` | `refund.created` | |
| `RefundCancelled` | `refund.cancelled` | |
| `RefundCaptureStarted` | `refund.capture_processing` | |
| `RefundCaptureFailed` | `refund.capture_failed` | |
| `RefundCaptureCompleted` | `refund.capture_completed` | |
| `RefundPayoutPending` | `refund.payout_processing` | |
| `RefundPayoutFailed` | `refund.payout_failed` | |
| `RefundPayoutCompleted` | `refund.payout_completed` | |
| — | `reverse_payout.created` | New in v3. **Confirm.** |
| — | `reverse_payout.cancelled` | New in v3. |
| `ReversePayoutStarted` | `reverse_payout.processing` | **Confirm.** |
| `ReversePayoutFailed` | `reverse_payout.failed` | |
| `ReversePayoutCompleted` | `reverse_payout.completed` | |
| `TransactionStarted` | `transaction.created` | |
| `TransactionCaptureStarted` | `transaction.processing` | |
| `TransactionAwaitingCancellation` | `transaction.awaiting_cancellation` | |
| `TransactionPaused` | `transaction.paused` | |
| `TransactionResumed` | `transaction.resumed` | |
| `TransactionCanceled` | `transaction.cancelled` | Note the spelling change. |
| `TransactionDeclined` | `transaction.declined` | |
| `TransactionFailed` | `transaction.failed` | |
| `TransactionCompleted` | `transaction.completed` | |
| — | `fbo.funding_created`, `fbo.funding_processing`, `fbo.funding_completed`, `fbo.funding_failed`, `fbo.funding_canceled` | New in v3. |
| — | `fbo.account_snapshot` | New in v3. |

## A suggested order of work

Every integration is different, but this sequence has worked well for teams we've talked to.

1. **Take inventory.** List every v1 and v2 endpoint your integration calls, and every legacy event your handler consumes. If you think you're on v2, check for v1 calls too; transaction creation and webhook registration almost always are.
2. **Map each call** using the tables above. If a row you depend on is marked for confirmation, or an endpoint you use isn't listed, ask us at [support@grailpay.com](mailto:support@grailpay.com) before you build against your guess.
3. **Rework entity handling.** Replace `users` calls with `people`, `businesses`, or `merchants`. Stop looking up user UUIDs to create transactions; pass the entity UUID you have. Store bank account UUIDs, since the aggregator-type path segment is gone.
4. **Rework webhook handling.** Register one webhook through `POST /api/v3/webhooks`; there is no event list to maintain. Change your handler to read `event` and `data`, route on `event` and drop the events you don't need, and use `event_uuid` to de-duplicate.
5. **Test in Sandbox.** v3 is available in the sandbox environment. The [Technical Overview](/docs/technical/overview#testing) covers how to get set up there.
6. **Cut over, then clean up.** Once v3 is live for you, remove the v1 and v2 calls rather than leaving them dormant. After October 1, 2027 they'll fail anyway, and it's better to find any you missed on your own schedule.

## Reference

- [Payments API v3 reference](/api/payments/)
- [Payments API v2 reference (deprecated)](/api/payments/v2/)
- [Payments API v1 reference (deprecated)](/api/payments/v1/)
- [Webhook Events](/docs/technical/webhooks/events/)
- [Legacy Webhook Events](/docs/technical/webhooks/legacy-events/)

## Questions?

If something in your integration doesn't fit the tables above, or you'd like a second pair of eyes on your migration plan, reach out to [support@grailpay.com](mailto:support@grailpay.com). We'd rather hear from you early than have you discover a gap in September 2027.