CoinVoyage v3 is a breaking API and SDK update. The main change is the move from PayOrder terminology and helper names to Order terminology across the REST API, PayKit, webhooks, and WebSocket events.
v3 upgrade is breaking. Keep your v2 integration running until you have tested order creation, payment completion, webhooks, refunds, swaps, and any off-ramp workflows in your target environment.
What changes
| Area | v2 | v3 |
|---|
| API base URL | https://api.coinvoyage.io/v2 | https://api.coinvoyage.io/v3 |
| Primary resource name | PayOrder | Order |
| Server-created PayButton prop | payId | orderId |
| PayButton lifecycle start callback | onPaymentStarted | onAwaitingPayment, onConfirmingPayment, onExecutingPayment |
| Webhook payload event field | type with lowercase payorder_* values | event with uppercase ORDER_* values |
| Webhook payload resource ID | payorder_id | order.id |
Migration checklist
- Upgrade CoinVoyage dependencies to v3, including
@coin-voyage/paykit@3.0.0.
- Update
ApiClient PayOrder helper names with the v3 Order helper names.
- Update
PayButton and PayButton.Custom from payId to orderId.
- Replace
onPaymentStarted with the more specific payment lifecycle callbacks.
- Update webhook handlers to read
{ event, delivered_at, order }.
- Run a full test payment and verify that webhook fulfillment remains idempotent.
Upgrade PayKit
pnpm add @coin-voyage/paykit@3.0.0
Use the package manager your project already uses.
Update ApiClient initialization
ApiClient is now called as a factory function from @coin-voyage/paykit/server.
import { ApiClient } from "@coin-voyage/paykit/server";
const apiClient = ApiClient({
apiKey: process.env.COIN_VOYAGE_API_KEY!,
environment: "production",
});
When you manually sign HTTP requests, sign the path without the /v3 prefix:
const authorization = apiClient.generateAuthorizationSignature(
process.env.COIN_VOYAGE_API_SECRET!,
"POST",
"/orders"
);
Rename order methods
Replace v2 PayOrder helper names with v3 Order helper names.
| v2 method | v3 method |
|---|
createDepositPayOrder(params) | createDepositOrder(params) |
createSalePayOrder(params, apiSecret) | createSaleOrder(params, apiSecret) |
createRefundPayOrder(orderId, params, apiSecret) | createRefundOrder(orderId, params, apiSecret) |
getPayOrder(payOrderId) | getOrder(orderId) |
listPayOrders(params, apiSecret) | listOrders(params, apiSecret) |
payOrderQuote(orderId, params) | orderQuotes(orderId, params) |
payOrderPaymentDetails(params) | createPayment(orderId, params) |
getPayOrderPaymentMethods(orderId) | getOrderPaymentMethods(orderId) |
Deposit order before and after
const { data, error } = await apiClient.createDepositPayOrder({
intent: {
asset: {
chain_id: ChainId.SUI,
address: null,
},
amount: {
token_amount: 10,
},
receiving_address: "0xDestinationWallet",
},
});
Sale order before and after
const { data, error } = await apiClient.createSalePayOrder(
{
intent: {
amount: {
fiat: {
amount: 49.99,
unit: "USD",
},
},
},
metadata: {
order_id: "order_123",
},
},
process.env.COIN_VOYAGE_API_SECRET!
);
Use orderId for server-created orders. Client-side deposit buttons still use toChain, toToken, toAmount, and toAddress.
<PayButton.Custom
payId={payId}
onPaymentStarted={(event) => {
console.log("Payment started", event);
}}
onPaymentCompleted={(event) => {
console.log("Payment completed", event);
}}
>
{({ show }) => <button onClick={show}>Pay</button>}
</PayButton.Custom>
The split callbacks give you clearer UI states:
| v3 callback | When it fires |
|---|
onAwaitingPayment | Payment details are available and the user can fund the order. |
onConfirmingPayment | The source transaction is detected and waiting for confirmation. |
onExecutingPayment | CoinVoyage is executing the destination transfer or contract call. |
onPaymentCompleted | The order completed successfully. |
onPaymentBounced | The destination call reverted and funds were refunded. |
Update provider configuration
PayKitProvider no longer documents the v2 theme preset prop or options.embedGoogleFonts. Use mode and customTheme instead.
<PayKitProvider
apiKey={process.env.NEXT_PUBLIC_COIN_VOYAGE_API_KEY!}
theme="midnight"
options={{
language: "en",
embedGoogleFonts: true,
}}
>
{children}
</PayKitProvider>
For Sui wallet configuration, rename config.sui.rpcUrl to config.sui.grpcUrl:
<WalletProvider
config={{
sui: {
grpcUrl: "https://fullnode.mainnet.sui.io:443",
},
}}
>
{children}
</WalletProvider>
Update swaps
Standalone swaps still use swapQuote() for the quote step. Use swapExecute() for execution data.
const { data, error } = await apiClient.swapData(params);
Update off-ramp flows
v3 names the fiat payout workflow as off-ramp verification, bank accounts, and off-ramp intents.
| v2 method | v3 method |
|---|
createKYCLink(params, apiSecret) | createOffRampVerification(params, apiSecret) |
getKYCStatus(apiSecret) | getOffRampVerificationStatus(apiSecret) |
listWithdrawals(apiSecret) | listOffRampIntents(apiSecret) |
createWithdrawal(params, apiSecret) | createOffRampIntent(params, apiSecret) |
Bank-account helpers remain part of the off-ramp workflow: listBankAccounts(apiSecret), getBankAccount(bankAccountId, apiSecret), and addBankAccount(params, apiSecret).
Update webhooks and WebSockets
v3 webhook and /v3/ws deliveries use the same event envelope:
{
"event": "ORDER_COMPLETED",
"delivered_at": "2026-06-23T12:34:56Z",
"order": {
"id": "cabc1234567890abcdef12",
"mode": "SALE",
"status": "COMPLETED",
"metadata": {
"order_id": "order_123"
}
}
}
Update handlers that previously read lowercase type values such as payorder_completed.
switch (payload.type) {
case "payorder_completed":
await fulfill(payload.payorder_id);
break;
}
Use these v3 event identifiers when subscribing to or dispatching lifecycle events:
ORDER_CREATED
ORDER_AWAITING_PAYMENT
ORDER_CONFIRMING
ORDER_EXECUTING
ORDER_COMPLETED
ORDER_ERROR
ORDER_REFUNDED
ORDER_EXPIRED
ORDER_PARTIAL_PAYMENT
Test before switching production traffic
Before you route production users to v3:
- Create a
DEPOSIT order and verify the destination wallet receives funds.
- Create a server-side
SALE order and render it with orderId.
- Verify
ORDER_COMPLETED, failed, expired, refunded, and partial-payment webhook handling.
- Run a refund through
createRefundOrder().
- Run
swapQuote() and swapExecute() if your product exposes standalone swaps.
- Run the off-ramp verification and off-ramp intent flow if you use fiat payouts.
- Confirm your webhook handler is idempotent by
event, delivered_at, order.id, and your internal ID in order.metadata.
Reference pages