= {
fontFamily: "Arial, Helvetica, sans-serif",
accent: "#d6296f",
accentHover: "#b8225f",
accentText: "#ffffff",
radius: "16px",
};
export function BrandedSwap() {
return (
);
}
```
## Theme properties
SwapKit maps these properties to CSS custom properties inside the widget. Use the property names in the first column when passing `theme`.
| Property | CSS custom property | Controls |
| ----------------- | ----------------------- | -------------------------------------------------------------------------------------- |
| `fontFamily` | `--sk-font-family` | Font family for the widget and its controls. Load any custom font in your application. |
| `background` | `--sk-background` | Widget background. |
| `surface` | `--sk-surface` | Surface background. |
| `surfaceElevated` | `--sk-surface-elevated` | Elevated surface background. |
| `border` | `--sk-border` | Border color. |
| `text` | `--sk-text` | Main text color. |
| `textMuted` | `--sk-text-muted` | Secondary text color. |
| `accent` | `--sk-accent` | Accent color for actions and focus outlines. |
| `accentHover` | `--sk-accent-hover` | Accent color on hover. |
| `accentText` | `--sk-accent-text` | Text color on accent backgrounds. |
| `danger` | `--sk-danger` | Error color. |
| `warning` | `--sk-warning` | Warning color. |
| `success` | `--sk-success` | Success color. |
| `overlay` | `--sk-overlay` | Overlay background. |
| `shadow` | `--sk-shadow` | Box shadow. |
| `radius` | `--sk-radius` | Base corner radius, including a CSS unit such as `"16px"`. |
## Light and dark themes
Set `mode="light"` or `mode="dark"` for a fixed appearance. With `mode="auto"` (the default), SwapKit follows the user's OS color scheme preference.
Your `theme` overrides are applied to both built-in themes. Properties you omit retain their light or dark defaults. The example above customizes the accent, font, and corner radius while letting backgrounds and text adapt automatically.
If you override backgrounds and text colors, choose values that work together in both modes, or pass a different `theme` when your application's theme changes and set `mode` to match.
# CoinVoyage SwapKit — embeddable token swaps
Source: https://docs.coinvoyage.io/sdk/swapkit
Install @coin-voyage/swapkit to add a cross-chain token swap experience to your React application.
Use `@coin-voyage/swapkit` when you want to add token swap capabilities to your application. It gives your users an embeddable interface for connecting a wallet, selecting source and destination tokens, requesting a quote, configuring slippage, executing the swap, and tracking its status.
SwapKit is independent from the SALE and DEPOSIT payment flows provided by PayKit. If your application needs to accept customer payments or fund a specified wallet or account, use [`@coin-voyage/paykit`](/sdk/overview).
## Installation
Install SwapKit with its core peer dependencies:
```bash npm theme={null}
npm i @coin-voyage/swapkit @tanstack/react-query react react-dom styled-components
```
```bash pnpm theme={null}
pnpm add @coin-voyage/swapkit @tanstack/react-query react react-dom styled-components
```
```bash yarn theme={null}
yarn add @coin-voyage/swapkit @tanstack/react-query react react-dom styled-components
```
```bash bun theme={null}
bun add @coin-voyage/swapkit @tanstack/react-query react react-dom styled-components
```
Install the wallet peer dependencies listed by `@coin-voyage/swapkit` for the chains your application supports. SwapKit uses `styled-components` and does not require Tailwind CSS.
## Add the swap widget
Wrap `Swap` with the re-exported `WalletProvider` and `SwapKitProvider`:
```tsx theme={null}
"use client";
import { Swap, SwapKitProvider, WalletProvider } from "@coin-voyage/swapkit";
export function SwapWidget() {
return (
);
}
```
`defaultSourceCurrency` and `defaultDestinationCurrency` accept a chain ID and an optional token address. Defaults are read when the widget mounts; remount the widget with a new React `key` to apply changed defaults.
## Configure the swap experience
* Use `defaultSlippageBps` to start with a custom slippage tolerance. When you omit it, SwapKit lets the quote service choose the tolerance automatically.
* Use `onComplete` to receive the completed swap's `orderId` and `transactionHash`.
* Use `onError` to surface execution errors in your application.
* Set `mode` on `SwapKitProvider` to `"light"`, `"dark"`, or `"auto"`, and pass `theme` to customize the widget. See [Styling / Theme](/sdk/styling/swapkit) for an example and all theme properties.
`SwapKitProvider` uses the public API client and accepts an API key, but never an
admin signing key.
For a custom swap interface or backend integration, see the [ApiClient swap methods](/sdk/apiclient/swaps).
# useOrderStatusWS - order status WebSocket hook
Source: https://docs.coinvoyage.io/sdk/useorderstatusws
Subscribe to CoinVoyage order status events from React with automatic reconnects and connection-state callbacks.
`useOrderStatusWS` subscribes to order status events over the CoinVoyage backend WebSocket from inside a React component. Use it when you need live payment state outside the default `PayButton` lifecycle callbacks, such as a custom checkout page, payment status panel, or embedded dashboard view.
`useOrderStatusWS` must run inside `PayKitProvider` because it uses the configured backend API client from context.
## Import
```tsx theme={null}
import { useOrderStatusWS } from "@coin-voyage/paykit";
```
## Basic usage
```tsx theme={null}
"use client";
import { useState } from "react";
import { useOrderStatusWS } from "@coin-voyage/paykit";
export function OrderStatusPanel({ orderId }: { orderId?: string }) {
const [connected, setConnected] = useState(false);
useOrderStatusWS({
orderId,
enabled: Boolean(orderId),
onConnectedChange: setConnected,
onEvent: (event) => {
console.log("Order event:", event);
},
onError: (error) => {
console.error("Order status stream failed:", error);
},
});
return {connected ? "Connected" : "Connecting"}
;
}
```
## Options
Order ID to subscribe to. The hook does not connect until `orderId` is available.
Controls whether the WebSocket should be active. Set this to `false` to pause the subscription without unmounting the component.
Called for each order event delivered by the backend WebSocket.
Called when the WebSocket errors, authentication times out, or the backend returns an error message.
Called when the connection becomes authenticated or disconnects.
## Behavior
* Connects only when `enabled` is true and `orderId` is present.
* Subscribes to the specified order after the WebSocket opens.
* Waits for backend authentication before reporting `connected: true`.
* Reconnects with exponential backoff after close, error, or authentication timeout.
* Cleans up timers and closes the socket when the component unmounts or dependencies change.
Use this hook for UI responsiveness only. Fulfillment should still be driven by verified webhooks on your server.
# WalletProvider — configure multi-chain wallet connectors
Source: https://docs.coinvoyage.io/sdk/walletprovider
Configure WalletProvider with chain-specific settings for EVM, Solana, Sui, and UTXO wallets including RPC URLs, adapters, and connectors.
`WalletProvider` manages wallet connections for the CoinVoyage PayKit SDK. It wraps `PayKitProvider` in your provider tree and is required to use `PayButton` and `usePayStatus`. Without it, the payment modal cannot connect to a user's wallet. You configure it through a `config` prop that accepts per-chain settings — you only need to supply configuration for the chains your application uses.
## Setup
Import `WalletProvider` from `@coin-voyage/paykit` and wrap it around `PayKitProvider`:
```tsx theme={null}
"use client";
import { PayKitProvider, WalletProvider } from "@coin-voyage/paykit";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient();
export function Providers({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
```
All `config` fields are optional. You can pass `` with no props and the SDK will use its default connector configuration.
## Configuration options
Object containing chain-type-specific wallet configuration. Every sub-key is optional — include only the chains you need to customize.
### config.evm
Configuration for EVM-compatible chains (Ethereum, Polygon, Arbitrum, Base, etc.). Lets you configure the bundled wallet connectors and add custom ones.
Configuration passed to the WalletConnect connector.
* `projectId` (string) — your WalletConnect Cloud project ID. Required to enable WalletConnect across all EVM chains.
Configuration passed to the Coinbase Wallet connector.
* `appName` (string) — the display name shown in the Coinbase Wallet app during connection.
Configuration passed to the MetaMask connector. Useful when you need to customize MetaMask-specific behavior or pass additional connector options.
Optional RPC transport configuration keyed by EVM chain ID.
Custom Wagmi connector factories to include with the default EVM connectors.
When `true`, lazy-loads wallet SDKs unless the wallet was the most recently connected wallet.
### config.solana
Configuration for the Solana chain. Provide a custom RPC endpoint or extend the list of supported wallet adapters.
Custom Solana RPC URL. Use this to point to your own node or a premium RPC provider instead of the default public endpoint.
Additional wallet adapter configuration.
* `wallets` (array) — array of Solana wallet adapter instances to add alongside the default set.
### config.sui
Configuration for the Sui chain.
Custom Sui gRPC URL for connecting to a specific full node.
Additional wallet adapter configuration for the Sui chain.
### config.utxo
Configuration for UTXO-based chains such as Bitcoin.
When `true`, defers loading UTXO wallet connectors until they are actually needed. This can reduce initial bundle parsing time in applications where UTXO payments are infrequent.
Optional Bitcoin RPC transport configuration.
## Examples
Use `WalletProvider` with no configuration when the SDK defaults are sufficient:
```tsx theme={null}
{children}
```
Supply your WalletConnect Cloud project ID to enable QR code and deep link connections across EVM chains:
```tsx theme={null}
{children}
```
Point the Solana connector at a private RPC and add a custom wallet adapter:
```tsx theme={null}
import { PhantomWalletAdapter } from "@solana/wallet-adapter-wallets";
{children}
```
# CoinVoyage webhook event types and payload reference
Source: https://docs.coinvoyage.io/webhooks/events
Every CoinVoyage order webhook event with its subscription identifier and v3 payload structure.
CoinVoyage emits webhook events at each stage of the order lifecycle. For every event you subscribe to, CoinVoyage delivers a `POST` request containing an event envelope and the current order snapshot.
## Event types
Subscription identifiers and delivered payload values are the same uppercase `ORDER_*` strings.
| Event | Description |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
| `ORDER_CREATED` | A new order was created. |
| `ORDER_AWAITING_PAYMENT` | The order is ready and awaiting payment from the user. |
| `ORDER_CONFIRMING` | Payment has been detected and is being confirmed on-chain. |
| `ORDER_EXECUTING` | CoinVoyage is executing the destination transfer or contract call. |
| `ORDER_COMPLETED` | The order completed successfully. |
| `ORDER_ERROR` | An error occurred during processing. |
| `ORDER_REFUNDED` | Funds were refunded to the user. |
| `ORDER_EXPIRED` | The order expired before payment was received. |
| `ORDER_PARTIAL_PAYMENT` | The user sent less than the required amount. Pay to Address can accept additional deposits; other payment methods need resolution. |
## Base payload
Every webhook delivery uses this envelope:
```json theme={null}
{
"event": "ORDER_COMPLETED",
"delivered_at": "2026-06-23T12:34:56Z",
"order": {
"id": "cabc1234567890abcdef12",
"created_at": "2026-06-23T12:30:00Z",
"updated_at": "2026-06-23T12:34:55Z",
"organization_id": "org_123",
"mode": "SALE",
"status": "COMPLETED",
"fulfillment": {
"amount": {
"ui": "49.99",
"raw": "49990000",
"value_usd": 49.99
},
"fiat_unit": "USD"
},
"payment": {
"payment_rail": "CRYPTO",
"recipient": "0xMerchantWallet",
"source_tx_hash": "0xabc123",
"destination_tx_hash": "0xdef456",
"steps": [],
"expires_at": "2026-06-23T13:00:00Z"
},
"metadata": {
"order_id": "order_123"
},
"hosted_url": "https://pay.coinvoyage.io/pay/cabc1234567890abcdef12"
}
}
```
| Field | Description |
| ---------------- | ------------------------------------------------------------------------------------------------------- |
| `event` | Uppercase `ORDER_*` identifier for the lifecycle event. |
| `delivered_at` | Event publish time. The value is stable across webhook retries and WebSocket pushes for the same event. |
| `order` | Current order snapshot in the v3 `OrderResponse` shape. |
| `order.id` | CoinVoyage order ID. Store this for reconciliation and idempotency. |
| `order.status` | Current lifecycle status. |
| `order.metadata` | Optional metadata attached when the order was created. Use this to map back to your internal records. |
| `order.payment` | Payment details. Present after payment instructions have been created. |
## Payment object
When present, `order.payment` contains the funding input, resolved output, rail-specific instructions, transaction hashes, and expiry.
```json theme={null}
{
"payment_rail": "CRYPTO",
"input": {
"currency": {
"chain_id": 1,
"address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"symbol": "USDC",
"decimals": 6
},
"amount": {
"ui": "49.99",
"raw": "49990000",
"value_usd": 49.99
}
},
"output": {
"currency": {
"chain_id": 30000000000002,
"address": null,
"symbol": "SUI",
"decimals": 9
},
"amount": {
"ui": "22.4",
"raw": "22400000000",
"value_usd": 49.99
}
},
"funding": {
"required_amount": {
"ui": "49.99",
"raw": "49990000",
"value_usd": 49.99
},
"received_amount": {
"ui": "20.00",
"raw": "20000000",
"value_usd": 20
},
"remaining_amount": {
"ui": "29.99",
"raw": "29990000",
"value_usd": 29.99
},
"transactions": [
{
"tx_hash": "0xabc123",
"amount": {
"ui": "20.00",
"raw": "20000000",
"value_usd": 20
}
}
]
},
"recipient": "0xMerchantWallet",
"refund": {
"address": "0xRefundAddress",
"tx_hash": null,
"reason": null
},
"source_tx_hash": "0xabc123",
"destination_tx_hash": "0xdef456",
"fee_tx_hash": null,
"steps": [
{
"rail": "CRYPTO",
"kind": "deposit",
"deposit_address": "0xDepositAddress",
"data": {
"deposit_address": "0xDepositAddress",
"currency": {
"chain_id": 1,
"address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
},
"amount": "49990000"
}
}
],
"expires_at": "2026-06-23T13:00:00Z"
}
```
## Payload examples
The examples below highlight event-specific fields. Production deliveries include the full `order` snapshot described above.
```json theme={null}
{
"event": "ORDER_CREATED",
"delivered_at": "2026-06-23T12:30:00Z",
"order": {
"id": "cabc1234567890abcdef12",
"mode": "SALE",
"status": "PENDING",
"metadata": {
"order_id": "order_123"
}
}
}
```
```json theme={null}
{
"event": "ORDER_AWAITING_PAYMENT",
"delivered_at": "2026-06-23T12:31:00Z",
"order": {
"id": "cabc1234567890abcdef12",
"mode": "SALE",
"status": "AWAITING_PAYMENT",
"payment": {
"payment_rail": "CRYPTO",
"recipient": "0xMerchantWallet",
"steps": [],
"expires_at": "2026-06-23T13:00:00Z"
}
}
}
```
```json theme={null}
{
"event": "ORDER_CONFIRMING",
"delivered_at": "2026-06-23T12:32:00Z",
"order": {
"id": "cabc1234567890abcdef12",
"mode": "SALE",
"status": "AWAITING_CONFIRMATION",
"payment": {
"source_tx_hash": "0xabc123",
"destination_tx_hash": null
}
}
}
```
```json theme={null}
{
"event": "ORDER_EXECUTING",
"delivered_at": "2026-06-23T12:33:00Z",
"order": {
"id": "cabc1234567890abcdef12",
"mode": "SALE",
"status": "EXECUTING_ORDER",
"payment": {
"source_tx_hash": "0xabc123",
"destination_tx_hash": null
}
}
}
```
```json theme={null}
{
"event": "ORDER_COMPLETED",
"delivered_at": "2026-06-23T12:34:00Z",
"order": {
"id": "cabc1234567890abcdef12",
"mode": "SALE",
"status": "COMPLETED",
"payment": {
"source_tx_hash": "0xabc123",
"destination_tx_hash": "0xdef456"
}
}
}
```
```json theme={null}
{
"event": "ORDER_ERROR",
"delivered_at": "2026-06-23T12:34:00Z",
"order": {
"id": "cabc1234567890abcdef12",
"mode": "SALE",
"status": "FAILED"
}
}
```
```json theme={null}
{
"event": "ORDER_REFUNDED",
"delivered_at": "2026-06-23T12:34:00Z",
"order": {
"id": "cabc1234567890abcdef12",
"mode": "SALE",
"status": "REFUNDED",
"payment": {
"refund": {
"address": "0xRefundAddress",
"tx_hash": "0xrefund123",
"reason": "execution failed"
}
}
}
}
```
```json theme={null}
{
"event": "ORDER_EXPIRED",
"delivered_at": "2026-06-23T13:00:00Z",
"order": {
"id": "cabc1234567890abcdef12",
"mode": "SALE",
"status": "EXPIRED"
}
}
```
For Pay to Address, use `payment.funding.remaining_amount` to tell the payer what is still due. Each additional deposit is appended to `payment.funding.transactions`. Keep fulfillment paused until a later event reports `ORDER_COMPLETED`.
```json theme={null}
{
"event": "ORDER_PARTIAL_PAYMENT",
"delivered_at": "2026-06-23T12:34:00Z",
"order": {
"id": "cabc1234567890abcdef12",
"mode": "SALE",
"status": "PARTIAL_PAYMENT",
"payment": {
"source_tx_hash": "0xabc123",
"funding": {
"required_amount": { "ui": "49.99", "raw": "49990000" },
"received_amount": { "ui": "20.00", "raw": "20000000" },
"remaining_amount": { "ui": "29.99", "raw": "29990000" },
"transactions": [
{
"tx_hash": "0xabc123",
"amount": { "ui": "20.00", "raw": "20000000" }
}
]
}
}
}
}
```
Additional-deposit recovery is available only for Pay to Address. Do not treat this webhook as resumable for other payment methods.
# Set up webhooks for real-time payment notifications
Source: https://docs.coinvoyage.io/webhooks/overview
Register a CoinVoyage webhook endpoint, verify HMAC-SHA256 delivery signatures, and handle order lifecycle events in your server.
Webhooks let you receive real-time notifications whenever an order changes status. Rather than polling the CoinVoyage API for updates, you register an HTTPS endpoint and CoinVoyage delivers a `POST` request with the event details each time something happens: payment created, awaiting payment, confirming, executing, completed, refunded, expired, failed, or partially paid.
## Set up a webhook
Register and manage webhook endpoints in the CoinVoyage Dashboard under the **Developers** section.
Open the [CoinVoyage Dashboard](https://dashboard.coinvoyage.io/developers), select **Developers** in the sidebar, then click **Webhooks**.
Click the **Add Webhook** button to open the registration form.
Provide the URL of your publicly accessible webhook handler.
Your endpoint must use HTTPS in production. HTTP endpoints are not accepted.
Choose which order lifecycle events should trigger delivery to your endpoint. Subscription event identifiers use uppercase `ORDER_*` format, for example `ORDER_COMPLETED`.
Save the webhook. CoinVoyage generates a **Webhook Secret**. Store it securely, for example as `COIN_VOYAGE_WEBHOOK_SECRET`. You need this secret to verify the signature on every incoming request.
Store the Webhook Secret in an environment variable, never in source code or version control.
Your endpoint must be publicly accessible and respond with a `2xx` status code within 30 seconds. Responses outside this window are treated as delivery failures.
## Delivery payload
CoinVoyage v3 delivers the same event envelope to registered webhooks and `/v3/ws` subscribers:
```json theme={null}
{
"event": "ORDER_COMPLETED",
"delivered_at": "2026-06-23T12:34:56Z",
"order": {
"id": "cabc1234567890abcdef12",
"mode": "SALE",
"status": "COMPLETED",
"metadata": {
"order_id": "order_123"
}
}
}
```
Use `event` to dispatch business logic and `order.id` as the CoinVoyage payment lifecycle ID. Use `order.metadata` to reconcile back to your internal order, invoice, account, or customer.
## Verify webhook signatures
Every webhook request includes a `CoinVoyage-Webhook-Signature` header containing an HMAC-SHA256 signature of the raw request body, encoded in Base64. Always verify this signature before parsing or acting on the payload.
The example below shows a complete Next.js Route Handler that verifies the signature and dispatches on the event identifier:
```typescript app/api/webhook/route.ts theme={null}
import { Buffer } from "buffer";
import { createHmac, timingSafeEqual } from "crypto";
const webhookSecret = process.env.COIN_VOYAGE_WEBHOOK_SECRET!;
type CoinVoyageWebhookEvent = {
event: string;
delivered_at: string;
order: {
id: string;
status: string;
metadata?: Record;
};
};
export const POST = async (req: Request) => {
const rawBody = await req.text();
const signature = req.headers.get("CoinVoyage-Webhook-Signature");
const expected = createHmac("sha256", webhookSecret)
.update(rawBody)
.digest("base64");
const signatureBytes = Buffer.from(signature ?? "");
const expectedBytes = Buffer.from(expected);
if (
signatureBytes.length !== expectedBytes.length ||
!timingSafeEqual(signatureBytes, expectedBytes)
) {
return new Response("Unauthorized", { status: 401 });
}
const event = JSON.parse(rawBody) as CoinVoyageWebhookEvent;
switch (event.event) {
case "ORDER_COMPLETED":
console.log("Order completed", event.order.id);
break;
case "ORDER_REFUNDED":
console.log("Order refunded", event.order.id);
break;
case "ORDER_EXPIRED":
console.log("Order expired", event.order.id);
break;
default:
console.log("Unhandled webhook", event.event, event.order.id);
}
return new Response("Webhook received", { status: 200 });
};
```
Read the raw request body with `req.text()` before calling `JSON.parse`. Web Fetch API request bodies can only be read once, so read once as text, verify the signature, then parse.
## Security best practices
* Verify the signature before parsing or acting on the payload.
* Use a constant-time comparison such as `timingSafeEqual` for signature checks.
* Use HTTPS in production.
* Store your Webhook Secret securely in a server-side secret store.
* Return a `2xx` response quickly and move heavy work to a queue or background job.
* Make event handling idempotent by `event`, `delivered_at`, and `order.id`, plus your own internal ID from `order.metadata`.
* Return `200` for event types you intentionally ignore so delivery does not retry forever.