# Get asset details
Source: https://docs.halliday.xyz/api-reference/assets/get-asset-details
public/openapi.yaml get /assets
Get detailed information about specific assets including metadata, symbols, and chain information.
# Get available input assets for a given output asset
Source: https://docs.halliday.xyz/api-reference/assets/get-available-input-assets-for-a-given-output-asset
public/openapi.yaml get /assets/available-inputs
Get a list of assets that can be used as inputs for the given output assets and providers.
# Get available output assets for a given input asset
Source: https://docs.halliday.xyz/api-reference/assets/get-available-output-assets-for-a-given-input-asset
public/openapi.yaml get /assets/available-outputs
Get a list of assets that can be received as outputs for the given input assets and providers.
# Get supported chains
Source: https://docs.halliday.xyz/api-reference/chains/get-supported-chains
public/openapi.yaml get /chains
Get a list of all supported blockchain networks with their configuration details.
# Confirm a payment
Source: https://docs.halliday.xyz/api-reference/payments/confirm-a-payment
public/openapi.yaml post /payments/confirm
Confirm a previously quoted payment and receive deposit instructions.
Returns information on how to fund the payment (widget URL, contract address, etc.).
# Confirm withdrawal request
Source: https://docs.halliday.xyz/api-reference/payments/confirm-withdrawal-request
public/openapi.yaml post /payments/withdraw/confirm
Submit and execute the signed withdrawal request to return funds back to the owner or to a requoted processing address.
# Get payment history
Source: https://docs.halliday.xyz/api-reference/payments/get-payment-history
public/openapi.yaml get /payments/history
Retrieve a paginated list of payment statuses filtered by owner.
Returns an array of payment status objects and a pagination key for fetching the next page.
# Get payment quotes
Source: https://docs.halliday.xyz/api-reference/payments/get-payment-quotes
public/openapi.yaml post /payments/quotes
Request quotes for payments, supporting both fixed input and fixed output scenarios.
Returns multiple quote options with pricing, fees, and routing information.
This endpoint can also be used to requote from an existing payment by providing a payment_id.
When requoting, input amounts are automatically derived from
the payment's current state, but you can optionally override the output asset.
# Get payment status
Source: https://docs.halliday.xyz/api-reference/payments/get-payment-status
public/openapi.yaml get /payments
Get the current status of a payment, including progress through onramp/swap/offramp stages.
# Get wallet balances
Source: https://docs.halliday.xyz/api-reference/payments/get-wallet-balances
public/openapi.yaml post /payments/balances
Retrieve balances for the wallets associated with a payment. You can optionally supply additional
wallet and token pairs to check alongside the payment's primary wallet.
# Return funds to owner or retry payment
Source: https://docs.halliday.xyz/api-reference/payments/return-funds-to-owner-or-retry-payment
public/openapi.yaml post /payments/withdraw
Request a withdrawal to return funds back to the owner or to a requoted processing address.
This endpoint should be used when a payment cannot be completed and funds need to be returned.
# Payment Recoveries API Errors
Source: https://docs.halliday.xyz/pages/api-error-recovery-withdrawal
In the event that a payment fails to complete there are several options for a user to recover their assets.
### Causes of payment interruptions
Crypto deposits require orchestration of several independent protocols like bridges, DEXs, blockchains, onramp providers and more. Mid-workflow, an asset's price could change significantly, a fee could increase beyond the bounds of the quote, an onramp could experience an unexpected delay or other unforseen setbacks.
Halliday Payments was built to be robust under all of these conditions within the ever-growing ecosystem of onchain protocols.
The Halliday payments [OTWs](/pages/otw) are **self-custodial**. Users are always the sole controllers of these addresses through their wallet.
## Recoveries
Halliday Payments provides two options for recovering assets within an interrupted payment.
### Retry a payment
An interrupted payment can be retried with a new quote. The new output amount may differ from the original quote based on asset price changes.
If a payment expires, and has not been funded, it is safe to abandon it and create a whole new payment.
Effectively, a new payment is created and then funded using the interrupted payment by transferring tokens from the old deposit address to the new deposit address using an EIP-712 signature. An [example of this signature request](#example-withdrawal-signature-request) is shown below.
**Retry a payment using the API**
* A funded payment is not completing.
* Query the [balances API endpoint](/api-reference/payments/get-wallet-balances) (`POST /payments/balances`) and pass the incomplete payment ID which we will call failed `payment_id`. This returns the deposit address (`address`) and the balance. Check that the balance is greater than zero. If so, a recovery can be performed.
* [Get new quotes](/api-reference/payments/get-payment-quotes) using `POST /payments/quotes`. Provide the failed `payment_id` as `parent_payment_id` in the request body.
* Once the user selects a quote, [confirm the new quote](/api-reference/payments/confirm-a-payment) using `POST /payments/confirm`. To do this, pass the new quote's payment ID, which we will call new `payment_id`, to the confirm endpoint.
* Next call the [withdraw endpoint](/api-reference/payments/return-funds-to-owner-or-retry-payment) (`POST /payments/withdraw`) with the parameters:
* `payment_id`: The failed payment's ID.
* `token_amount`: The returned amount of token from the prior `POST /payments/balances` endpoint call.
* `recipient_address`: The address of the new payment's deposit address.
* The owner wallet of the failed payment must sign an EIP-712 withdrawal transaction. This is returned from the `POST /payments/withdraw` call response object as the `withdraw_authorization` property.
* Next call the [confirm withdraw endpoint](/api-reference/payments/confirm-withdrawal-request) (`POST /payments/withdraw/confirm`) with the same parameters passed to the prior API call along with the newly created `owner_signature`. The withdrawal will be executed onchain automatically and transfer the assets from the old deposit address to the new one.
### Withdrawals
Assets lingering in a deposit address can be withdrawn to any address specified in a withdrawal signature created by the owner wallet. In some situations, this may result in the asset being moved to a user-controlled wallet on a different chain than the intended destination.
**Withdrawal steps using the API**
* A funded payment is not completing.
* Query the [balances API endpoint](/api-reference/payments/get-wallet-balances) (`POST /payments/balances`) and pass the incomplete payment ID which we will call failed `payment_id`. This returns the deposit address (`address`) and the balance. Check that the balance is greater than zero. If so, a recovery can be performed.
* Next call the [withdraw endpoint](/api-reference/payments/return-funds-to-owner-or-retry-payment) (`POST /payments/withdraw`) with the parameters:
* `payment_id`: The failed payment's ID.
* `token_amount`: The returned amount of token from the prior `POST /payments/balances` endpoint call.
* `recipient_address`: The address to withdraw the tokens to, usually the owner's wallet.
* The owner wallet of the failed payment must sign an EIP-712 withdrawal transaction. This is returned from the `POST /payments/withdraw` call response object as the `withdraw_authorization` property.
* Next call the [confirm withdraw endpoint](/api-reference/payments/confirm-withdrawal-request) (`POST /payments/withdraw/confirm`) with the same parameters passed to the prior API call along with the newly created `owner_signature`. This signature will be executed onchain automatically and transfer the assets.
### Example Withdrawal Signature Request
The following is an example withdrawal signature request on EVM chains using EIP-712 which is returned from the `POST /payments/withdraw` API endpoint.
The owner of the payment will generate a signature using their private key in order to confirm a withdrawal.
```json theme={null}
{
"domain": { "name": "Halliday Workflow Protocol", "version": "1" },
"types": {
"EIP712Domain": [ { "type": "string", "name": "name" }, { "type": "string", "name": "version" } ],
"Call": [ { "type": "address", "name": "target" }, { "type": "bytes", "name": "data" }, { "type": "uint256", "name": "value" } ],
"HallidayAccount": [ { "type": "string", "name": "description" }, { "type": "Call[]", "name": "actions" }, { "name": "nonce", "type": "uint256" }, { "type": "bytes32", "name": "signatory_declaration_hash" }, { "type": "uint256", "name": "chain_id" }, { "type": "bool", "name": "accept_blame" } ]
},
"primaryType": "HallidayAccount",
"message": {
"description": "Transfer 50 USDC on Base to address 0x...",
"actions": [
{
"target": "0xrecipient",
"data": "0xdata",
"value": "0"
}
],
"nonce": "0",
"signatory_declaration_hash": "0x123...",
"chain_id": "8453",
"accept_blame": true
}
}
```
### Recovery Implementation Options
To implement withdrawals and retries using the API directly, see [API Example Apps](/pages/api-example-apps).
Alternatively, payments can be recovered on the following page by connecting the payment's **owner address** wallet `https://app.halliday.xyz/funding/${payment_id}`.
## API Errors
The REST API returns formatted errors with a corresponding error code, such as 400 for bad request and 401 for unauthorized.
**Example 401 response**
```json theme={null}
{
"errors": [
{
"kind": "other",
"message": "Invalid public api key"
}
]
}
```
**Example 400 response**
```json theme={null}
{
"errors": [
{
"origin": "string",
"code": "invalid_format",
"format": "regex",
"pattern": "/^[a-zA-Z]\\w{1,7}$/",
"path": [ "inputs", 0 ],
"message": "Must be a short alpha-numeric symbol"
}
]
}
```
The `GET /assets/available-inputs` and `GET /assets/available-outputs` endpoints will return bad request errors in the scenario that an unsupported asset address is passed as a parameter.
# API Example Apps
Source: https://docs.halliday.xyz/pages/api-example-apps
These open source example apps demonstrate integrating the Halliday API directly into an app to enable fiat onramping, cross-chain swaps, payment retries, and processing address withdrawals.
For more rapid integrations that use the Halliday SDK widget with a whitelabel user interface see [Payments SDK Example Apps](/pages/payments-sdk-example-apps).
## Plain JavaScript API Examples
The following open source example app code demonstrates how to implement [fiat onramps](/pages/api-example-apps#fiat-onramps-via-api-using-javascript) and [cross-chain swaps](/pages/api-example-apps#cross-chain-swaps-via-api-using-javascript) using the Halliday API.
There are also API examples shown to handle [withdrawals](/pages/api-example-apps#withdraw-stuck-assets-with-the-api-using-javascript) and [retries](/pages/api-example-apps#retry-incomplete-payments-with-the-api-using-javascript) for payments that have failed to complete.
* [Plain JavaScript app example repository](https://github.com/HallidayInc/HallidayPaymentsApiExamples)
### Fiat onramps via API using JavaScript
This [example app](https://github.com/HallidayInc/HallidayPaymentsApiExamples) shows how to build a fiat-to-crypto onramp, with a custom user interface, using the Halliday Payments API.
In the example, a user would be able to onramp directly to IP on Story mainnet with a provider like Stripe, Transak, or Moonpay.
The app allows a user to input their Story mainnet address and the amount of USD they wish to spend.
Using JavaScript, the app fetches a collection of quotes from the API and displays the best price available with each provider, as well as the total amount of onramp fees.
```js theme={null}
// From getQuote function
const res = await fetch('https://v2.prod.halliday.xyz/payments/quotes', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + HALLIDAY_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({
request: {
kind: 'FIXED_INPUT',
fixed_input_amount: {
asset: inputAsset,
amount: inputAmount,
},
output_asset: outputAsset,
},
price_currency: 'usd',
onramps,
onramp_methods: fiatOnrampPayInMethods,
customer_geolocation: { alpha3_country_code: 'USA' }
}),
});
```
Once the user selects a quote and destination address in the user interface, clicking or tapping the Continue button confirms the quote with the API.
```js theme={null}
// From acceptQuote function
const res = await fetch('https://v2.prod.halliday.xyz/payments/confirm', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + HALLIDAY_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({
payment_id: selectedQuote.paymentId,
state_token: selectedQuote.stateToken,
owner_address: destinationAddress,
destination_address: destinationAddress
})
});
```
Next, the payment provider's checkout page is shown to the user. This is where the user would input their credit or debit card information for the transaction.
```js theme={null}
// From onContinueButtonClick function
const acceptedQuote = await acceptQuote();
const paymentId = acceptedQuote.payment_id;
const onrampUrl = acceptedQuote.next_instruction.funding_page_url;
paymentStatusInterval = setInterval(async () => {
console.log('payment status:', paymentId, await getPaymentStatus(paymentId));
}, 5000);
continueButton.classList.remove('loading');
onrampIframe.src = onrampUrl;
```
Once the checkout is completed, the payment workflow begins. The app polls the payment status endpoint and shows the current status of the payment in the user interface.
```js theme={null}
// From getPaymentStatus function
const res = await fetch(`https://v2.prod.halliday.xyz/payments?payment_id=${paymentId}`, {
method: 'GET',
headers: {
'Authorization': 'Bearer ' + HALLIDAY_API_KEY,
'Content-Type': 'application/json'
},
});
```
This example **does not show** an implementation of a recovery flow if the payment fails midway onchain. In the event a payment does not complete, a user can recover it by connecting the **owner** wallet on this page: `https://app.halliday.xyz/funding/${payment_id}`.
### Cross-chain swaps via API using JavaScript
This [example app](https://github.com/HallidayInc/HallidayPaymentsApiExamples) shows how to build a cross-chain swap app, with a custom user interface, using the Halliday Payments API.
In the example, a user would be able to swap from USDC on Base to IP on Story mainnet using the Halliday Payments API and their own wallet.
The app allows a user to connect their wallet and input the amount of USDC on Base that they wish to spend.
Using JavaScript, the app fetches a collection of quotes from the API and displays the best price available and the total amount of fees.
```js theme={null}
// From getQuote function
const res = await fetch('https://v2.prod.halliday.xyz/payments/quotes', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + HALLIDAY_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({
request: {
kind: 'FIXED_INPUT',
fixed_input_amount: {
asset: inputAsset,
amount: inputAmount
},
output_asset: outputAsset
},
price_currency: 'USD'
})
});
```
Once the user approves the quote, it is confirmed using the API.
```js theme={null}
const requestBody = {
payment_id: quote.paymentId,
state_token: quote.stateToken,
owner_address: userAddress,
destination_address: userAddress
};
const res = await fetch('https://v2.prod.halliday.xyz/payments/confirm', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + HALLIDAY_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify(requestBody)
});
```
Next, the user can sign transactions with their wallet to fund the workflow.
The latest status of the cross-chain swap is fetched from the API and displayed in the UI.
```js theme={null}
const res = await fetch('https://v2.prod.halliday.xyz/payments' +
`?payment_id=${paymentId}`, {
method: 'GET',
headers: {
'Authorization': 'Bearer ' + HALLIDAY_API_KEY,
'Content-Type': 'application/json'
}
});
```
The next section shows how to implement payment recoveries directly via API. Alternatively, payments can be recovered on the following page by connecting the payment's **owner address** wallet `https://app.halliday.xyz/funding/${payment_id}`.
### Withdraw stuck assets with the API using JavaScript
In the event that a payment begins its onchain steps and fails to complete them, or assets are sent to a processing address in which the payment is expired, a user can sign a transaction to withdraw the tokens to any address.
The owner address, which is usually the user's wallet, is the sole controller of assets in the processing addresses.
An overview and details on the withdrawal process are explained on the [API Recoveries & Errors page](/pages/api-error-recovery-withdrawal#withdrawals).
The app uses the history API endpoint to fetch payments for the wallet address.
```js theme={null}
async function getWalletPaymentHistory(address, paginationKey) {
const params = new URLSearchParams({
category: 'ALL',
owner_address: address,
...(paginationKey && { pagination_key: paginationKey })
});
const res = await fetch(`https://v2.prod.halliday.xyz/payments/history?${params}`, {
method: 'GET',
headers: {
'Authorization': 'Bearer ' + HALLIDAY_API_KEY,
'Content-Type': 'application/json'
}
});
// Note that only payments initialized with this HALLIDAY_API_KEY will be returned
const data = await res.json();
return data;
}
```
Here is example code to get the entire history of payments created using the API.
```js theme={null}
const payments = [];
let paginationKey;
do {
const history = await getWalletPaymentHistory(_userAddress, paginationKey);
if (history.next_pagination_key) {
paginationKey = history.next_pagination_key;
} else {
paginationKey = undefined;
}
payments.push(...history.payment_statuses);
} while (paginationKey)
```
Next, after filtering out properly completed payments, the balance of failed or expired payments is queried using the API one by one. This endpoint will get the current token balances for all of the payment's processing addresses on the relevant chains and return the data in the response.
```js theme={null}
async function getProcessingAddressBalances(paymentId) {
try {
const res = await fetch(`https://v2.prod.halliday.xyz/payments/balances`, {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + HALLIDAY_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({ payment_id: paymentId })
});
const data = await res.json();
return data;
} catch (e) {
console.error('getProcessingAddressBalances error', e);
}
}
```
Relevant payment information is rendered in the UI for the user. The user can tap or click buttons to initialize the withdrawal flow for each individual stuck token balance.
Initializing the withdrawal flow has three steps:
1. Get the EIP-712 data from the API (`getTypedData`) which is used to sign a transaction to withdraw the token from the processing address.
2. The transaction is signed (`signTypedData`) with the user wallet using Ether.js on the client.
3. The signed transaction data is then sent to the API (`confirmWithdrawal`) to confirm the withdrawal and execute it onchain. The API returns the onchain transaction hash in the response body.
```js theme={null}
withdrawButton.addEventListener('click', async () => {
withdrawButton.classList.add('loading');
// Fetch the withdraw signature data from the API
const withdrawToAddress = userAddress; // user's connected wallet
const typedDataToSign = await getTypedData(withdrawToAddress, paymentId, balance.token, balance.value.amount);
const { domain, types, message } = JSON.parse(typedDataToSign.withdraw_authorization);
delete types.EIP712Domain;
// Sign the withdraw transaction using Ethers
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
const signature = await signer.signTypedData(domain, types, message);
// Send signature to API to be posted onchain
const txHash = await confirmWithdrawal(withdrawToAddress, paymentId, balance.token, balance.value.amount, signature);
// Show the resulting withdraw transaction on the proper block explorer
const chain = balance.token.split(':')[0];
const { explorer } = supportedChains[chain];
const link = item.querySelector('a');
link.setAttribute('href', `${explorer}tx/${txHash}`);
link.classList.remove('hidden');
withdrawButton.disabled = true;
withdrawButton.classList.remove('loading');
});
```
Lastly, the transaction with the final transfer of tokens to the withdrawal address is rendered as a blockchain explorer link for the user to tap or click.
### Retry incomplete payments with the API using JavaScript
In the event an onramp or swap is funded but fails to complete, a retry can be attempted using assets lingering in a processing address. A retry requires a new quote which uses the lingering assets as the input token amount. Think of retries as a withdrawal of an old payment to deposit into a new payment.
The owner address is the sole controller of assets in the processing addresses. The user wallet must sign a transaction to initialize the retry.
Payment retries are further explained on the [API Recoveries & Errors page](/pages/api-error-recovery-withdrawal#retry-a-payment).
The retry example uses the same history API endpoint to fetch payments for the wallet address as the above withdraw example (see `getWalletPaymentHistory` above).
Also the same endpoint to fetch current processing address balances is used (see `getProcessingAddressBalances` above).
Using the balances response, a quote is made for bridging and swapping with the processing address balance as the input. This is the same quotes endpoint used in onramps and swaps. See the `getQuote` function in the cross-chain swaps example above.
```js theme={null}
const balance = balances.balance_results[i];
const _token = balance.token;
const _amount = +balance.value.amount;
if (_amount === 0) {
continue;
}
const quoteResult = await getQuote(balance.value.amount, _token, outputAsset, paymentId);
if (quoteResult.quotes.length === 0) {
alert('Retry not possible. Try withdrawal.');
continue;
}
```
Once the user accepts a quote available to retry the payment, the following four steps orchestrate a retry:
1. Use the API endpoint to accept the new quote (see `acceptQuote`). This is the same endpoint used to accept quotes in onramps and swaps.
2. Fetch the withdrawal signature data from the API (see `getTypedData`). This transaction will transfer tokens from the **old** payment to the **new** payment once it is executed onchain. This is the same gas-sponsored transaction concept explained in the above **withdraw** example.
3. The transaction is signed (`signTypedData`) with the user wallet using Ether.js on the client.
4. The signed transaction data is then sent to the API (`confirmWithdrawal`) to confirm the token transfer from the old payment to the new payment. The API then executes this transaction onchain covering all of the blockchain gas costs.
```js theme={null}
// Accept the retry quote
const acceptQuoteRequest = await acceptQuote(newPaymentId, newStateToken);
const statusElement = document.getElementById('status');
statusElement.innerText = `Status: ${acceptQuoteRequest.status}...`;
// Fetch the retry signature data from the API
const withdrawToAddress = acceptQuoteRequest.next_instruction.deposit_info[0].deposit_address; // new quoted payment's deposit address
const typedDataToSign = await getTypedData(withdrawToAddress, paymentId, balance.token, balance.value.amount);
const { domain, types, message } = JSON.parse(typedDataToSign.withdraw_authorization);
delete types.EIP712Domain;
// Sign the retry transfer transaction using Ethers
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
const signature = await signer.signTypedData(domain, types, message);
// Send signature to API to be posted onchain
await confirmWithdrawal(withdrawToAddress, paymentId, balance.token, balance.value.amount, signature);
```
The new payment ID can subsequently be tracked. The new payment will reach the `COMPLETED` state once the output tokens have reached the destination address.
## React API Examples
The following open source example app demonstrates building onramps, cross-chain swaps, payment retries, and processing address withdrawals using the Halliday API directly by connecting a user's EIP-1193 wallet like MetaMask or Rabby.
* [Halliday API Examples using React.js](https://github.com/HallidayInc/HallidayPaymentsApiExamplesReact)
## React API Examples with Dynamic Embedded Wallets
The following open source example app demonstrates building onramps, cross-chain swaps, payment retries, and processing address withdrawals using the Halliday API directly with a Dynamic embedded wallet.
* [Halliday API Examples with Dynamic Embedded Wallets and React.js](https://github.com/HallidayInc/HallidayApiPrivyReactExamples)
## React API Examples with Privy Wallet
The following open source example app demonstrates building onramps, cross-chain swaps, payment retries, and processing address withdrawals using the Halliday API directly with a Privy connected wallet.
* [Halliday API Examples with Privy Wallet and React.js](https://github.com/HallidayInc/HallidayApiPrivyReactExamples)
# Halliday API Quickstart
Source: https://docs.halliday.xyz/pages/api-quickstart
Halliday Payments is a unified deposits API combining a global on/off ramp network, cross-chain swap router, and centralized exchange connector into a single product.
The REST API is available to developers building custom user interfaces on any device.
Open source apps with custom web interfaces that use the REST API directly instead of the SDK are available in the [API Example Apps](/pages/api-example-apps) section.
For a simpler payments integration with a web user interface, see the [Payments widget](/pages/payments-sdk-docs) documentation.
### Authentication
All API requests require authentication using an API key in the `Authorization` HTTP request header. To get API keys, send an email to [partnerships@halliday.xyz](mailto:partnerships@halliday.xyz).
```
Authorization: Bearer pk_HALLIDAY_API_KEY_HERE
```
### One Time Wallet (OTW)
Each unique payment begins with a new onchain address called a **one-time wallet** (OTW) also known as the **deposit address**. The OTW is always different from the payment destination address. The Payments SDK widget UI refers to this address as a one-time wallet in the delivery details section.
Each onramp or swap will create a new OTW that is controlled only by the **owner wallet address** specified in the API call parameters. In the event a payment gets stuck or is funded after expiration, the owner address has the ability to sign transactions to recover the assets from the OTW or retry the payment.
A payment can be initiated by simply funding the OTW from any address. For fiat onramps specifically, the onramp providers will be instructed to send tokens to the deposit address.
## Discovering Available Assets
Halliday Payments supports hundreds of crypto assets for fiat onramps from centralized exchanges and payment providers, as well as onchain swaps via bridges and DEXs.
Before initalizing a payment, use this endpoint to see which crypto assets are supported.
### Get All Supported Assets
```bash cURL theme={null}
curl https://v2.prod.halliday.xyz/assets \
-H "Authorization: Bearer pk_HALLIDAY_API_KEY_HERE"
```
```javascript JavaScript theme={null}
const response = await fetch('https://v2.prod.halliday.xyz/assets', {
headers: {
'Authorization': 'Bearer pk_HALLIDAY_API_KEY_HERE'
}
});
const data = await response.json();
```
```python Python theme={null}
import urllib.request
request = urllib.request.Request(
'https://v2.prod.halliday.xyz/assets',
headers={'Authorization': 'Bearer pk_HALLIDAY_API_KEY_HERE'}
)
response = urllib.request.urlopen(request)
data = response.read()
```
```php PHP theme={null}
```
```go Go theme={null}
package main
import (
"net/http"
"io"
)
func main() {
req, _ := http.NewRequest("GET", "https://v2.prod.halliday.xyz/assets", nil)
req.Header.Set("Authorization", "Bearer pk_HALLIDAY_API_KEY_HERE")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
}
```
```java Java theme={null}
import java.net.HttpURLConnection;
import java.net.URL;
URL url = new URL("https://v2.prod.halliday.xyz/assets");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("Authorization", "Bearer pk_HALLIDAY_API_KEY_HERE");
conn.setRequestMethod("GET");
int status = conn.getResponseCode();
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
uri = URI('https://v2.prod.halliday.xyz/assets')
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer pk_HALLIDAY_API_KEY_HERE'
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
```
**Response:**
```json theme={null}
{
"usd": {
"issuer": "USA",
"name": "United States Dollar",
"symbol": "USD",
"decimals": 2,
"image_url": "..."
},
"arbitrum:0xaf88d065e77c8cc2239327c5edb3a432268e5831": {
"chain": "arbitrum",
"address": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
"name": "USD Coin",
"symbol": "USDC",
"decimals": 6,
"image_url": "...",
"is_native": false
},
"ethereum:0x": {
"chain": "ethereum",
"address": "0x",
"name": "Ether",
"symbol": "ETH",
"decimals": 18,
"image_url": "...",
"is_native": true
}
}
```
### Get valid deposit routes
To verify conversion from a specific input to a desired output, query both assets together like in the following example.
Get a list of assets that can be received as outputs for the given input assets and providers using `/assets/available-outputs`.
Each of the query parameters of `inputs` and `outputs` are both arrays of strings of asset IDs which are returned by `/assets`.
The `/assets/available-outputs` endpoint requires at least one input asset.
Additionally, onramp provider checks can be done by passing an `onramps` array of strings (`moonpay`, `coinbase`, et al). This filtering ensures a path from an input to an output can be achieved with a given onramp provider.
```bash cURL theme={null}
curl "https://v2.prod.halliday.xyz/assets/available-outputs\
?inputs[]=usd\
&outputs[]=ethereum:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" \
-H "Authorization: Bearer pk_HALLIDAY_API_KEY_HERE"
```
```javascript JavaScript theme={null}
const params = new URLSearchParams({
'inputs[]': 'usd',
'outputs[]': 'ethereum:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
});
const response = await fetch(`https://v2.prod.halliday.xyz/assets/available-outputs?${params}`, {
headers: {
'Authorization': 'Bearer pk_HALLIDAY_API_KEY_HERE'
}
});
const data = await response.json();
```
```python Python theme={null}
import urllib.request
import urllib.parse
params = urllib.parse.urlencode({
'inputs[]': 'usd',
'outputs[]': 'ethereum:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
})
url = f'https://v2.prod.halliday.xyz/assets/available-outputs?{params}'
request = urllib.request.Request(
url,
headers={'Authorization': 'Bearer pk_HALLIDAY_API_KEY_HERE'}
)
response = urllib.request.urlopen(request)
data = response.read()
```
```php PHP theme={null}
['usd'],
'outputs' => ['ethereum:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48']
]);
$url = 'https://v2.prod.halliday.xyz/assets/available-outputs?' . $params;
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer pk_HALLIDAY_API_KEY_HERE'
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
?>
```
```go Go theme={null}
package main
import (
"net/http"
"net/url"
"io"
)
func main() {
baseURL, _ := url.Parse("https://v2.prod.halliday.xyz/assets/available-outputs")
params := url.Values{}
params.Add("inputs[]", "usd")
params.Add("outputs[]", "ethereum:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48")
baseURL.RawQuery = params.Encode()
req, _ := http.NewRequest("GET", baseURL.String(), nil)
req.Header.Set("Authorization", "Bearer pk_HALLIDAY_API_KEY_HERE")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
}
```
```java Java theme={null}
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
String params = "inputs[]=usd&outputs[]=ethereum:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48";
String urlString = "https://v2.prod.halliday.xyz/assets/available-outputs?" + params;
URL url = new URL(urlString);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("Authorization", "Bearer pk_HALLIDAY_API_KEY_HERE");
conn.setRequestMethod("GET");
int status = conn.getResponseCode();
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
uri = URI('https://v2.prod.halliday.xyz/assets/available-outputs')
params = {
'inputs[]' => 'usd',
'outputs[]' => 'ethereum:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
}
uri.query = URI.encode_www_form(params)
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer pk_HALLIDAY_API_KEY_HERE'
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
```
This checks if USD can be converted to USDC on Ethereum. If the response includes the desired output asset, the route is supported.
**Response:**
```json theme={null}
{
"usd": {
"fiats": [],
"tokens": [
"ethereum:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
]
}
}
```
The response shows that USDC on Ethereum can be obtained from USD (fiat). A 200 OK empty object JSON string response `{}` would indicate that there is no route currently supported.
### Verify multiple routes in one request
The following is an example of providing more than one member to an array in the request query parameters.
```bash cURL theme={null}
curl "https://v2.prod.halliday.xyz/assets/available-outputs\
?inputs[]=usd\
&outputs[]=base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913\
&outputs[]=avalanche:0x" \
-H "Authorization: Bearer pk_HALLIDAY_API_KEY_HERE"
```
```javascript JavaScript theme={null}
const params = new URLSearchParams();
params.append('inputs[]', 'usd');
params.append('outputs[]', 'base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913');
params.append('outputs[]', 'avalanche:0x');
const response = await fetch(`https://v2.prod.halliday.xyz/assets/available-outputs?${params}`, {
headers: {
'Authorization': 'Bearer pk_HALLIDAY_API_KEY_HERE'
}
});
const data = await response.json();
```
```python Python theme={null}
import urllib.request
import urllib.parse
params = [
('inputs[]', 'usd'),
('outputs[]', 'base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913'),
('outputs[]', 'avalanche:0x')
]
url = f'https://v2.prod.halliday.xyz/assets/available-outputs?{urllib.parse.urlencode(params)}'
request = urllib.request.Request(
url,
headers={'Authorization': 'Bearer pk_HALLIDAY_API_KEY_HERE'}
)
response = urllib.request.urlopen(request)
data = response.read()
```
```php PHP theme={null}
['usd'],
'outputs' => [
'base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',
'avalanche:0x'
]
]);
$url = 'https://v2.prod.halliday.xyz/assets/available-outputs?' . $params;
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer pk_HALLIDAY_API_KEY_HERE'
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
?>
```
```go Go theme={null}
package main
import (
"net/http"
"net/url"
"io"
)
func main() {
baseURL, _ := url.Parse("https://v2.prod.halliday.xyz/assets/available-outputs")
params := url.Values{}
params.Add("inputs[]", "usd")
params.Add("outputs[]", "base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913")
params.Add("outputs[]", "avalanche:0x")
baseURL.RawQuery = params.Encode()
req, _ := http.NewRequest("GET", baseURL.String(), nil)
req.Header.Set("Authorization", "Bearer pk_HALLIDAY_API_KEY_HERE")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
}
```
```java Java theme={null}
import java.net.HttpURLConnection;
import java.net.URL;
String params = "inputs[]=usd&outputs[]=base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913&outputs[]=avalanche:0x";
String urlString = "https://v2.prod.halliday.xyz/assets/available-outputs?" + params;
URL url = new URL(urlString);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("Authorization", "Bearer pk_HALLIDAY_API_KEY_HERE");
conn.setRequestMethod("GET");
int status = conn.getResponseCode();
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
uri = URI('https://v2.prod.halliday.xyz/assets/available-outputs')
params = [
['inputs[]', 'usd'],
['outputs[]', 'base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913'],
['outputs[]', 'avalanche:0x']
]
uri.query = URI.encode_www_form(params)
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer pk_HALLIDAY_API_KEY_HERE'
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
```
The response confirms that USDC on Base and AVAX on Avalanche can be obtained from USD (fiat).
```json theme={null}
{
"usd": {
"fiats": [],
"tokens": [
"base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
"avalanche:0x"
]
}
}
```
## Fiat-to-Crypto Onramping with the API
Halliday Payments supports fiat onramp providers like Stripe, Moonpay, Transak and cryptocurrency exchanges (CEX) to enable users to compliantly onramp using fiat in their region with the best price available.
This enables end users to pay with credit or debit card, Apple Pay, Google Pay, or a centralized exchange balance with one simple interface.
### Get a collection of quotes for an onramp
When a user is ready to onramp, make an API request to generate quotes based on the amount of fiat the user desires to spend.
The Halliday API queries onramp providers to create quotes for the delivery of tokens onchain. The API intelligently surfaces providers based on the detected or provided IP address.
Additional checks are performed to identify if bridge and DEX calls are necessary to deliver the desired output token. All quoted routes are returned to the client with fees included.
This data can then be dispalyed in a UI so the user can select the their preferred provider and quote.
All quotes can be requested through the unified `POST /payments/quotes` endpoint.
#### Example: Quoting fiat-to-crypto onramp
Request a quote for purchasing USDC on Base with USD:
```bash cURL theme={null}
curl -X POST "https://v2.prod.halliday.xyz/payments/quotes" \
-H "Authorization: Bearer pk_HALLIDAY_API_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"request": {
"kind": "FIXED_INPUT",
"fixed_input_amount": {
"asset": "USD",
"amount": "100"
},
"output_asset": "base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"
},
"price_currency": "USD",
"onramps": [ "MOONPAY", "TRANSAK", "STRIPE" ],
"onramp_methods": [ "CREDIT_CARD", "ACH" ],
"customer_ip_address": ""
}'
```
```javascript JavaScript theme={null}
const response = await fetch('https://v2.prod.halliday.xyz/payments/quotes', {
method: 'POST',
headers: {
'Authorization': 'Bearer pk_HALLIDAY_API_KEY_HERE',
'Content-Type': 'application/json'
},
body: JSON.stringify({
request: {
kind: 'FIXED_INPUT',
fixed_input_amount: {
asset: 'USD',
amount: '100'
},
output_asset: 'base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913'
},
price_currency: 'USD',
onramps: ['MOONPAY', 'TRANSAK', 'STRIPE'],
onramp_methods: ['CREDIT_CARD', 'ACH'],
customer_ip_address: ''
})
});
const data = await response.json();
```
```python Python theme={null}
import urllib.request
import json
data = {
'request': {
'kind': 'FIXED_INPUT',
'fixed_input_amount': {
'asset': 'USD',
'amount': '100'
},
'output_asset': 'base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913'
},
'price_currency': 'USD',
'onramps': ['MOONPAY', 'TRANSAK', 'STRIPE'],
'onramp_methods': ['CREDIT_CARD', 'ACH'],
'customer_ip_address': ''
}
request = urllib.request.Request(
'https://v2.prod.halliday.xyz/payments/quotes',
data=json.dumps(data).encode('utf-8'),
headers={
'Authorization': 'Bearer pk_HALLIDAY_API_KEY_HERE',
'Content-Type': 'application/json'
},
method='POST'
)
response = urllib.request.urlopen(request)
result = response.read()
```
```php PHP theme={null}
[
'kind' => 'FIXED_INPUT',
'fixed_input_amount' => [
'asset' => 'USD',
'amount' => '100'
],
'output_asset' => 'base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913'
],
'price_currency' => 'USD',
'onramps' => ['MOONPAY', 'TRANSAK', 'STRIPE'],
'onramp_methods' => ['CREDIT_CARD', 'ACH'],
'customer_ip_address' => ''
];
$ch = curl_init('https://v2.prod.halliday.xyz/payments/quotes');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer pk_HALLIDAY_API_KEY_HERE',
'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
?>
```
```go Go theme={null}
package main
import (
"net/http"
"bytes"
"encoding/json"
"io"
)
func main() {
data := map[string]interface{}{
"request": map[string]interface{}{
"kind": "FIXED_INPUT",
"fixed_input_amount": map[string]string{
"asset": "USD",
"amount": "100",
},
"output_asset": "base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
},
"price_currency": "USD",
"onramps": []string{"MOONPAY", "TRANSAK", "STRIPE"},
"onramp_methods": []string{"CREDIT_CARD", "ACH"},
"customer_ip_address": "",
}
jsonData, _ := json.Marshal(data)
req, _ := http.NewRequest("POST", "https://v2.prod.halliday.xyz/payments/quotes", bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer pk_HALLIDAY_API_KEY_HERE")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
}
```
```java Java theme={null}
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.OutputStream;
URL url = new URL("https://v2.prod.halliday.xyz/payments/quotes");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Authorization", "Bearer pk_HALLIDAY_API_KEY_HERE");
conn.setRequestProperty("Content-Type", "application/json");
conn.setDoOutput(true);
String jsonData = "{\"request\":{\"kind\":\"FIXED_INPUT\",\"fixed_input_amount\":{\"asset\":\"USD\",\"amount\":\"100\"},\"output_asset\":\"base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913\"},\"price_currency\":\"USD\",\"onramps\":[\"MOONPAY\",\"TRANSAK\",\"STRIPE\"],\"onramp_methods\":[\"CREDIT_CARD\",\"ACH\"],\"customer_ip_address\":\"\"}";
try (OutputStream os = conn.getOutputStream()) {
os.write(jsonData.getBytes());
}
int status = conn.getResponseCode();
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
require 'json'
uri = URI('https://v2.prod.halliday.xyz/payments/quotes')
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer pk_HALLIDAY_API_KEY_HERE'
request['Content-Type'] = 'application/json'
request.body = {
request: {
kind: 'FIXED_INPUT',
fixed_input_amount: {
asset: 'USD',
amount: '100'
},
output_asset: 'base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913'
},
price_currency: 'USD',
onramps: ['MOONPAY', 'TRANSAK', 'STRIPE'],
onramp_methods: ['CREDIT_CARD', 'ACH'],
customer_ip_address: ''
}.to_json
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
```
**Response:**
```json theme={null}
{
"quote_request": {
"request": {
"kind": "FIXED_INPUT",
"fixed_input_amount": {
"asset": "usd",
"amount": "100"
},
"output_asset": "base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"
},
"price_currency": "usd",
"onramps": [
"moonpay",
"transak",
"stripe"
],
"onramp_methods": [
"CREDIT_CARD",
"ACH"
]
},
"quotes": [
{
"payment_id": "v4uuid...",
"onramp": "stripe",
"onramp_method": "CREDIT_CARD",
"output_amount": {
"asset": "base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
"amount": "96.11"
},
"fees": {
"total_fees": "3.90941422",
"conversion_fees": "3.90941422",
"network_fees": "0",
"business_fees": "0",
"currency_symbol": "usd"
},
"route": [...]
}
],
"current_prices": {
"usd": "1.0",
"base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913": "0.999798",
"base:0x": "4058.01",
"avalanche:0xb97ef9ef8734c71904d8002f8b6bc66dd9c48a6e": "0.999798",
"avalanche:0x": "19.8",
"arbitrum:0xaf88d065e77c8cc2239327c5edb3a432268e5831": "0.999798",
"arbitrum:0x": "4058.01"
},
"price_currency": "usd",
"state_token": "H4sIAAA...",
"quoted_at": "2025-10-28T19:29:18.351Z",
"accept_by": "2025-10-28T19:34:18.351Z",
"failures": []
}
```
### Confirm and fund the onramp
Once the user selects a quote, the onramp payment needs to be confirmed with the API before the quote expires.
Using the `payment_id` and `state_token` returned in the quote, a request is made to confirm the quote and specify the owner and destination. The owner and destination wallet addresses can be different.
The private key of the owner address is the **sole controller** of the one-time wallet. In the scenario that a recovery is needed, the owner address will need to sign transactions. See [API Recoveries & Errors](/pages/api-error-recovery-withdrawal) for more details.
This request (`POST /payments/confirm`) returns the same response body that the payment status endpoint (`GET /payments`) returns.
Within the `next_instruction` response body object, there is a `funding_page_url` string. This URL navigates the user to the payment page where they input their fiat payment information with the onramp provider. Developers are expected to display this web page to the user.
In the use case that a user does not fund the onramp directly, the `deposit_address` (OTW) can be funded by anyone with the `deposit_amount` to initiate the onramp.
#### Example: Confirm fiat-to-crypto onramp
```bash cURL theme={null}
curl -X POST "https://v2.prod.halliday.xyz/payments/confirm" \
-H "Authorization: Bearer pk_HALLIDAY_API_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"payment_id": "v4uuid...",
"state_token": "H4sIAAA...",
"owner_address": "0xowner...",
"destination_address": "0xdestination..."
}'
```
```javascript JavaScript theme={null}
const response = await fetch('https://v2.prod.halliday.xyz/payments/confirm', {
method: 'POST',
headers: {
'Authorization': 'Bearer pk_HALLIDAY_API_KEY_HERE',
'Content-Type': 'application/json'
},
body: JSON.stringify({
payment_id: 'v4uuid...',
state_token: 'H4sIAAA...',
owner_address: '0xowner...',
destination_address: '0xdestination...'
})
});
const data = await response.json();
```
```python Python theme={null}
import urllib.request
import json
data = {
'payment_id': 'v4uuid...',
'state_token': 'H4sIAAA...',
'owner_address': '0xowner...',
'destination_address': '0xdestination...'
}
request = urllib.request.Request(
'https://v2.prod.halliday.xyz/payments/confirm',
data=json.dumps(data).encode('utf-8'),
headers={
'Authorization': 'Bearer pk_HALLIDAY_API_KEY_HERE',
'Content-Type': 'application/json'
},
method='POST'
)
response = urllib.request.urlopen(request)
result = response.read()
```
```php PHP theme={null}
'v4uuid...',
'state_token' => 'H4sIAAA...',
'owner_address' => '0xowner...',
'destination_address' => '0xdestination...'
];
$ch = curl_init('https://v2.prod.halliday.xyz/payments/confirm');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer pk_HALLIDAY_API_KEY_HERE',
'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
?>
```
```go Go theme={null}
package main
import (
"net/http"
"bytes"
"encoding/json"
"io"
)
func main() {
data := map[string]string{
"payment_id": "v4uuid...",
"state_token": "H4sIAAA...",
"owner_address": "0xowner...",
"destination_address": "0xdestination...",
}
jsonData, _ := json.Marshal(data)
req, _ := http.NewRequest("POST", "https://v2.prod.halliday.xyz/payments/confirm", bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer pk_HALLIDAY_API_KEY_HERE")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
}
```
```java Java theme={null}
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.OutputStream;
URL url = new URL("https://v2.prod.halliday.xyz/payments/confirm");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Authorization", "Bearer pk_HALLIDAY_API_KEY_HERE");
conn.setRequestProperty("Content-Type", "application/json");
conn.setDoOutput(true);
String jsonData = "{\"payment_id\":\"v4uuid...\",\"state_token\":\"H4sIAAA...\",\"owner_address\":\"0xowner...\",\"destination_address\":\"0xdestination...\"}";
try (OutputStream os = conn.getOutputStream()) {
os.write(jsonData.getBytes());
}
int status = conn.getResponseCode();
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
require 'json'
uri = URI('https://v2.prod.halliday.xyz/payments/confirm')
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer pk_HALLIDAY_API_KEY_HERE'
request['Content-Type'] = 'application/json'
request.body = {
payment_id: 'v4uuid...',
state_token: 'H4sIAAA...',
owner_address: '0xowner...',
destination_address: '0xdestination...'
}.to_json
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
```
**Response:**
```json theme={null}
{
"payment_id": "v4uuid...",
"status": "PENDING",
"funded": false,
"created_at": "2025-10-28T19:30:12.818Z",
"updated_at": "2025-10-28T19:30:12.818Z",
"initiate_fund_by": "2025-10-28T19:40:12.818Z",
"quote_request": {
"request": {
"kind": "FIXED_INPUT",
"fixed_input_amount": {
"asset": "usd",
"amount": "100"
},
"output_asset": "base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"
},
"price_currency": "usd",
"onramps": [ "moonpay", "transak", "stripe" ],
"onramp_methods": [ "CREDIT_CARD", "ACH" ]
},
"quoted": {
"onramp": "stripe",
"onramp_method": "CREDIT_CARD",
"output_amount": {
"asset": "base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
"amount": "96.11"
},
"fees": {
"total_fees": "3.90941422",
"conversion_fees": "3.90941422",
"network_fees": "0",
"business_fees": "0",
"currency_symbol": "usd"
},
"route": []
},
"fulfilled": {
"onramp": "stripe",
"onramp_method": "CREDIT_CARD",
"route": [...]
},
"current_prices": {...},
"price_currency": "usd",
"processing_addresses": [],
"owner_address": "0xowner...",
"destination_address": "0xdestination...",
"next_instruction": {
"type": "ONRAMP",
"payment_id": "v4uuid...",
"funding_page_url": "https://app.halliday.xyz/funding/_payment_id_goes_here_?token=_token_",
"deposit_info": [
{
"deposit_token": "base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
"deposit_amount": "96.11",
"deposit_address": "0xdeposit...",
"deposit_chain": "base"
}
]
}
}
```
## Cross-chain swaps with the API
Halliday Payments brings together bridges, DEXs, and cross-chain routing logic to enable users to seamlessly swap to any token on any chain.
### Get a collection of quotes for a swap
When a user is ready to swap tokens, make an API request to generate quotes based on the amount the user desires to swap.
The Halliday API queries all available cross-chain routes supported by the protocol, optimizing current bridge, DEX, and transfer fees. All quoted onchain routes are returned to the client, including all fees.
This data can then be dispalyed in a UI so the user can select the their preferred route and quote.
All quotes are requested through the unified `POST /payments/quotes` endpoint.
#### Example: Quote a crypto-to-crypto swap
Request a quote for swapping AVAX on Avalanche to USDC on Base:
```bash cURL theme={null}
curl -X POST "https://v2.prod.halliday.xyz/payments/quotes" \
-H "Authorization: Bearer pk_HALLIDAY_API_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"request": {
"kind": "FIXED_INPUT",
"fixed_input_amount": {
"asset": "avalanche:0x",
"amount": "50"
},
"output_asset": "base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"
},
"price_currency": "USD"
}'
```
```javascript JavaScript theme={null}
const response = await fetch('https://v2.prod.halliday.xyz/payments/quotes', {
method: 'POST',
headers: {
'Authorization': 'Bearer pk_HALLIDAY_API_KEY_HERE',
'Content-Type': 'application/json'
},
body: JSON.stringify({
request: {
kind: 'FIXED_INPUT',
fixed_input_amount: {
asset: 'avalanche:0x',
amount: '50'
},
output_asset: 'base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913'
},
price_currency: 'USD'
})
});
const data = await response.json();
```
```python Python theme={null}
import urllib.request
import json
data = {
'request': {
'kind': 'FIXED_INPUT',
'fixed_input_amount': {
'asset': 'avalanche:0x',
'amount': '50'
},
'output_asset': 'base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913'
},
'price_currency': 'USD'
}
request = urllib.request.Request(
'https://v2.prod.halliday.xyz/payments/quotes',
data=json.dumps(data).encode('utf-8'),
headers={
'Authorization': 'Bearer pk_HALLIDAY_API_KEY_HERE',
'Content-Type': 'application/json'
},
method='POST'
)
response = urllib.request.urlopen(request)
result = response.read()
```
```php PHP theme={null}
[
'kind' => 'FIXED_INPUT',
'fixed_input_amount' => [
'asset' => 'avalanche:0x',
'amount' => '50'
],
'output_asset' => 'base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913'
],
'price_currency' => 'USD'
];
$ch = curl_init('https://v2.prod.halliday.xyz/payments/quotes');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer pk_HALLIDAY_API_KEY_HERE',
'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
?>
```
```go Go theme={null}
package main
import (
"net/http"
"bytes"
"encoding/json"
"io"
)
func main() {
data := map[string]interface{}{
"request": map[string]interface{}{
"kind": "FIXED_INPUT",
"fixed_input_amount": map[string]string{
"asset": "avalanche:0x",
"amount": "50",
},
"output_asset": "base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
},
"price_currency": "USD",
}
jsonData, _ := json.Marshal(data)
req, _ := http.NewRequest("POST", "https://v2.prod.halliday.xyz/payments/quotes", bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer pk_HALLIDAY_API_KEY_HERE")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
}
```
```java Java theme={null}
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.OutputStream;
URL url = new URL("https://v2.prod.halliday.xyz/payments/quotes");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Authorization", "Bearer pk_HALLIDAY_API_KEY_HERE");
conn.setRequestProperty("Content-Type", "application/json");
conn.setDoOutput(true);
String jsonData = "{\"request\":{\"kind\":\"FIXED_INPUT\",\"fixed_input_amount\":{\"asset\":\"avalanche:0x\",\"amount\":\"50\"},\"output_asset\":\"base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913\"},\"price_currency\":\"USD\"}";
try (OutputStream os = conn.getOutputStream()) {
os.write(jsonData.getBytes());
}
int status = conn.getResponseCode();
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
require 'json'
uri = URI('https://v2.prod.halliday.xyz/payments/quotes')
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer pk_HALLIDAY_API_KEY_HERE'
request['Content-Type'] = 'application/json'
request.body = {
request: {
kind: 'FIXED_INPUT',
fixed_input_amount: {
asset: 'avalanche:0x',
amount: '50'
},
output_asset: 'base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913'
},
price_currency: 'USD'
}.to_json
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
```
**Response:**
```json theme={null}
{
"quote_request": {
"request": {
"kind": "FIXED_INPUT",
"fixed_input_amount": {
"asset": "avalanche:0x",
"amount": "50"
},
"output_asset": "base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"
},
"price_currency": "usd"
},
"quotes": [
{
"payment_id": "v4uuid...",
"output_amount": {
"asset": "base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
"amount": "963.20128"
},
"fees": {
"total_fees": "1.89407692672",
"conversion_fees": "1.89407692672",
"network_fees": "0",
"business_fees": "0",
"currency_symbol": "usd"
},
"route": [...]
}
],
"current_prices": {
"usd": "1.0",
"avalanche:0x": "19.3",
"avalanche:0xb97ef9ef8734c71904d8002f8b6bc66dd9c48a6e": "0.999901",
"base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913": "0.999901",
"base:0x": "3956.77",
"arbitrum:0xaf88d065e77c8cc2239327c5edb3a432268e5831": "0.999901",
"arbitrum:0x": "3956.77",
"base:0x4200000000000000000000000000000000000006": "3957.08",
"base:0x0b3e328455c4059eeb9e3f84b5543f74e24e7e1b": "1.45",
"arbitrum:0x7f9fbf9bdd3f4105c478b996b648fe6e828a1e98": "0.416717"
},
"price_currency": "usd",
"state_token": "H4sIAAA...",
"quoted_at": "2025-10-28T21:27:54.994Z",
"accept_by": "2025-10-28T21:32:54.994Z",
"failures": []
}
```
### Confirm and fund the swap
Once the user selects a quote, the swap needs to be confirmed with the API before the quote expires.
Using the `payment_id` and `state_token` returned in the quote, a request is made to confirm the quote and specify the owner and destination. The owner and destination wallet addresses can be different.
The private key of the owner address is the **sole controller** of the one-time wallet. In the scenario that a recovery is needed, the owner address will need to sign transactions. See [API Recoveries & Errors](/pages/api-error-recovery-withdrawal) for more details.
This request (`POST /payments/confirm`) returns the same response body that the payment status endpoint (`GET /payments`) returns.
Within the `next_instruction` response body object, the `type` will be `TRANSFER_IN` string. This means that the `deposit_address` can be funded by anyone with the `deposit_amount` on the source chain. Transferring tokens to this OTW address initiates the swap.
#### Example: Confirm crypto-to-crypto swap
```bash cURL theme={null}
curl -X POST "https://v2.prod.halliday.xyz/payments/confirm" \
-H "Authorization: Bearer pk_HALLIDAY_API_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"payment_id": "v4uuid...",
"state_token": "H4sIAAA...",
"owner_address": "0xowner...",
"destination_address": "0xdestination..."
}'
```
```javascript JavaScript theme={null}
const response = await fetch('https://v2.prod.halliday.xyz/payments/confirm', {
method: 'POST',
headers: {
'Authorization': 'Bearer pk_HALLIDAY_API_KEY_HERE',
'Content-Type': 'application/json'
},
body: JSON.stringify({
payment_id: 'v4uuid...',
state_token: 'H4sIAAA...',
owner_address: '0xowner...',
destination_address: '0xdestination...'
})
});
const data = await response.json();
```
```python Python theme={null}
import urllib.request
import json
data = {
'payment_id': 'v4uuid...',
'state_token': 'H4sIAAA...',
'owner_address': '0xowner...',
'destination_address': '0xdestination...'
}
request = urllib.request.Request(
'https://v2.prod.halliday.xyz/payments/confirm',
data=json.dumps(data).encode('utf-8'),
headers={
'Authorization': 'Bearer pk_HALLIDAY_API_KEY_HERE',
'Content-Type': 'application/json'
},
method='POST'
)
response = urllib.request.urlopen(request)
result = response.read()
```
```php PHP theme={null}
'v4uuid...',
'state_token' => 'H4sIAAA...',
'owner_address' => '0xowner...',
'destination_address' => '0xdestination...'
];
$ch = curl_init('https://v2.prod.halliday.xyz/payments/confirm');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer pk_HALLIDAY_API_KEY_HERE',
'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
?>
```
```go Go theme={null}
package main
import (
"net/http"
"bytes"
"encoding/json"
"io"
)
func main() {
data := map[string]string{
"payment_id": "v4uuid...",
"state_token": "H4sIAAA...",
"owner_address": "0xowner...",
"destination_address": "0xdestination...",
}
jsonData, _ := json.Marshal(data)
req, _ := http.NewRequest("POST", "https://v2.prod.halliday.xyz/payments/confirm", bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer pk_HALLIDAY_API_KEY_HERE")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
}
```
```java Java theme={null}
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.OutputStream;
URL url = new URL("https://v2.prod.halliday.xyz/payments/confirm");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Authorization", "Bearer pk_HALLIDAY_API_KEY_HERE");
conn.setRequestProperty("Content-Type", "application/json");
conn.setDoOutput(true);
String jsonData = "{\"payment_id\":\"v4uuid...\",\"state_token\":\"H4sIAAA...\",\"owner_address\":\"0xowner...\",\"destination_address\":\"0xdestination...\"}";
try (OutputStream os = conn.getOutputStream()) {
os.write(jsonData.getBytes());
}
int status = conn.getResponseCode();
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
require 'json'
uri = URI('https://v2.prod.halliday.xyz/payments/confirm')
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer pk_HALLIDAY_API_KEY_HERE'
request['Content-Type'] = 'application/json'
request.body = {
payment_id: 'v4uuid...',
state_token: 'H4sIAAA...',
owner_address: '0xowner...',
destination_address: '0xdestination...'
}.to_json
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
```
**Response:**
```json theme={null}
{
"payment_id": "v4uuid...",
"status": "PENDING",
"funded": false,
"created_at": "2025-10-28T21:28:46.034Z",
"updated_at": "2025-10-28T21:28:46.872Z",
"initiate_fund_by": "2025-10-28T21:38:45.902Z",
"quote_request": {
"request": {
"kind": "FIXED_INPUT",
"fixed_input_amount": {
"asset": "avalanche:0x",
"amount": "50"
},
"output_asset": "base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"
},
"price_currency": "usd"
},
"quoted": {
"output_amount": {
"asset": "base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
"amount": "963.20128"
},
"fees": {
"total_fees": "1.89407692672",
"conversion_fees": "1.89407692672",
"network_fees": "0",
"business_fees": "0",
"currency_symbol": "usd"
},
"route": [...]
},
"fulfilled": {...},
"current_prices": {...},
"price_currency": "usd",
"processing_addresses": [...],
"owner_address": "0xowner...",
"destination_address": "0xdestination...",
"next_instruction": {
"type": "TRANSFER_IN",
"payment_id": "v4uuid...",
"funding_page_url": "https://app.halliday.xyz/funding/_payment_id_goes_here_?token=_token_",
"deposit_info": [
{
"deposit_token": "avalanche:0x",
"deposit_amount": "50",
"deposit_address": "0xdeposit...",
"deposit_chain": "avalanche"
}
]
}
}
```
The response includes the full payment status with confirmation details, tracking information, and funding status.
## Tracking Payment Status
This endpoint can be polled to get the latest **status** of the payment.
The `payment_id` parameter is from the chosen quote returned from the quotes endpoint and also the confirm endpoint. Note that the response from `POST /payments/confirm` is the same as the response body returned from `GET /payments`.
For more information on the API's payment statuses and depositing to initialize a payment workflow see [Status API](/pages/api-status).
## Error Handling and Recoveries
For information on payment errors, recovering failed payments, and withdrawing assets from an OTW, see [Payment API Errors & Recoveries](/pages/api-error-recovery-withdrawal).
In addition to the option of creating an API integration for recoveries, payments can be recovered on the following page by connecting the payment's **owner address** wallet `https://app.halliday.xyz/funding/${payment_id}`.
# Payment Funding & Status
Source: https://docs.halliday.xyz/pages/api-status
The `GET /payments` [status endpoint](/api-reference/payments/get-payment-status) can be polled to retrieve the full scope and current status of a payment using its unique `payment_id`. This endpoint supports fiat onramps, centralized exchange withdrawals, and cross-chain swaps initiated through the API or the Payments SDK widget.
During the payment lifecycle, the status response provides essential **funding information**. Once a payment is funded, the predefined onchain steps **execute automatically**.
Note that the JSON response object returned by `GET /payments` and `POST /payments/confirm` share the same structure and both represent the payment’s current status.
## Funding a payment
A payment is initiated onchain once the OTW [deposit address](/pages/otw) on the specified network holds a token balance **greater than or equal to** the input amount set in the quote. Sending tokens to this deposit address is referred to as a **deposit**. Any address can perform the deposit to fund the payment.
Once a fiat-to-crypto onramp, centralized exchange withdrawal, or cross-chain swap is confirmed using the `POST /payments/confirm` endpoint, the API returns a payment status object with a status of `PENDING`. Once a payment is pending, it can be funded. Funding must occur before the quote expires. The payment expires if it is not funded before the `initiate_fund_by` datetime detailed in the status object.
### Funding an onramp vs funding a swap
Fiat onramps are funded onchain **automatically** by the provider once a user completes the checkout on the provider's page.
A swap must be funded by transferring tokens to the deposit address onchain. For most use cases of swaps, the user transfers the input token to the deposit address from their own wallet. However any wallet may fund a payment.
Developers can find the specific details for funding a payment by viewing the `next_instruction` object.
### Next instruction
The JSON response object returned from the status and confirm endpoints contains a `next_instruction` object while the payment status is `PENDING`. This object details the next instruction required to execute the payment.
**Type**
The `next_instruction.type` value can be `ONRAMP` for fiat onramp payments or `TRANSFER_IN` for swaps.
**Funding page URL for fiat onramps**
The `next_instruction.funding_page_url` value is a unique URL. The URL redirects to a fiat onramp provider's checkout page (i.e. Stripe, Moonpay). On this page, the user confirms their fiat payment information.
**Deposit information**
The `next_instruction.deposit_info` value is an array of objects. This information is used to fund a payment.
* `deposit_token` The token to send to the deposit address in `${chain}:${token_address}` format.
* `deposit_amount` The amount of the token to send to the deposit address.
* `deposit_address` The onchain deposit address (OTW).
* `deposit_chain` Name of the chain on which to perform the deposit.
Example `next_instruction.deposit_info`:
```json theme={null}
[{
"deposit_token": "base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
"deposit_amount": "53.26",
"deposit_address": "0x39bd1CfcF898A3a3d1e6EFD33e1F65499e6326a9",
"deposit_chain": "base"
}]
```
**Pitfalls**
Note that EVM wallets have identical addresses on every EVM chain. The deposit is required to be done only on the chain specified in the deposit information.
Depositing to the OTW address on the wrong chain will not execute a payment and is to be avoided.
## Payment Statuses
The `status` property in the JSON response object is a string indicating the present status of the payment. Each of the following statuses can be returned for an onramp or swap payment.
### Pending
The `PENDING` status is the inital state of a confirmed payment. This state indicates that the payment is ready to be funded.
The specified onchain steps begin executing once the OTW (deposit address) is issued the proper amount of tokens on the proper chain.
See [funding a payment](#funding-a-payment) above for more details and [funding examples](#funding-examples) below.
### Expired
The `EXPIRED` status indicates that the payment was not funded before the `initiate_fund_by` datetime detailed in the status response object.
A payment can be safely abandoned if it becomes `EXPIRED`, before it is funded, in favor of a new quote. If an `EXPIRED` payment is funded, the `owner` wallet address can retry the payment with a new quote or withdraw the assets from the OTW to the `owner` address.
Withdrawal requires creation of an EIP-712 signature which the API can use to execute the transfer.
For more information on withdrawals and payment recoveries see the [Payment API Errors & Recoveries](/pages/api-error-recovery-withdrawal) guide.
### Complete
The `COMPLETE` status indicates that a payment has successfully reached the intended final onchain step detailed in the `route` object of the status response. There are no other possible states for the payment to change to once it has reached `COMPLETE`.
For example, a user onramps from USD to IP on Story Mainnet. The payment status changes to `COMPLETE` as soon as the transaction to transfer IP tokens to the user's wallet address on Story Mainnet has been confirmed.
A user will be shown the `COMPLETE` status in the Payments SDK widget transaction details only after the output tokens are in their wallet.
### Withdrawn
The `WITHDRAWN` status indicates that the funding amount deposited to the OTW has been successfully transferred out of the deposit address following execution of a withdrawal request. There are no other possible states for the payment to change to once it has reached `WITHDRAWN`.
### Tainted
The `TAINTED` status indicates that an onchain address associated with a payment request is recognized as a sanctioned address. Tainted payments cannot be completed or retried.
## Funding Examples
Both fiat onramps and swaps are funded in the same manner. Each unique payment has a unique onchain deposit address that begins execution of the payment steps once it is adequately funded.
Any subsequent onchain actions are performed automatically using predefined workflows. The user does not need to be mindful of gas tokens, bridges, or swaps that occur during the payment lifecycle, or take any other action in order for it to complete.
The result of a payment is that a destination wallet address holds, at minimum, the quoted amount of a destination token.
### Swap
After the user indicates their input token amount, chooses a quote, and the payment is confirmed, the deposit can be performed.
An app can prompt the user to transfer tokens in their wallet to the deposit address. Also, any funder wallet can perform the deposit.
### Fiat onramp
Just like a swap, the user indicates the amount that they want to spend, chooses a quote, and the funding process begins.
The user provides payment details via the next instruction's `funding_page_url`.
Once the checkout is completed, the onramp provider automatically funds the deposit address on the proper chain.
## Payment transaction hash
The structure of a Halliday Payment workflow contains several steps that occur in succession on one or more blockchains.
The user will input fiat or crypto and expect that a different asset arrives in their wallet address within seconds. Usually a payment will undergo several intermediary steps like DEX swaps and bridging before completing.
The final transfer step can be found publicly on a blockchain by its transaction hash.
### Complete payment status
Each individual Halliday Payment status object will contain the onchain transaction hash once the workflow has reached the `COMPLETE` step. The status object can be found using either of these two [Halliday API endpoints](https://docs.halliday.xyz/api-reference/).
* `GET /payments` using the `payment_id` (UUID string) as a query parameter which returns a single status object.
* `GET /payments/history` using the `owner_address` (wallet address string) as a query parameter, which returns a collection of status objects.
### Get an onchain transaction hash
Each status object in which the status is presently `COMPLETE` will include transaction hash strings among other data.
Transaction hashes returned from the status API are shown as `tx_id` in `fulfilled.route` array objects of type `ONCHAIN_STEP` within `net_effect.consume` or `net_effect.produce` array objects. Not every step will have a transaction hash associated with it.
The final `net_effect.produce` object in which the `account` is `DEST` will have a `tx_id` in which the output token is transferred to the destination address on the destination chain. Most commonly this is the end user's wallet address on the output token's chain.
Here is a JavaScript example to fetch the transaction hash with the transfer of tokens to the user's wallet from a `COMPLETE` status object.
```js theme={null}
const txHash = data.fulfilled.route
.filter(step => step.type === "ONCHAIN_STEP")
.flatMap(step => step.net_effect.produce)
.find(p => p.account === "DEST" && p.tx_id)
?.tx_id;
```
Here is an alternative example that uses for loops
```js theme={null}
let txHash;
for (const step of data.fulfilled.route) {
if (step.type !== "ONCHAIN_STEP") continue;
for (const p of step.net_effect.produce) {
if (p.account === "DEST" && p.tx_id) {
txHash = p.tx_id;
break;
}
}
if (txHash) break;
}
```
Here is a stripped-down example of a `COMPLETE` payment status object that contains transaction hashes.
```json theme={null}
{
"payment_id": "41b16a6f-704e-44ba-9964-2b66df8a73e8",
"status": "COMPLETE",
"fulfilled": {
"output_amount": {
"asset": "chain:0xtoken_contract_address",
"amount": "0.0911234"
},
"fees": {},
"route": [
{
"type": "USER_FUND",
},
{
"type": "ONCHAIN_STEP",
"status": "COMPLETE",
"net_effect": {}
},
{
"type": "ONCHAIN_STEP",
"status": "COMPLETE",
"net_effect": {
"consume": [
{
"account": "SPW",
"tx_id": "0xanother_tx_hash_here"
}
],
"produce": [
{
"account": "DEST",
"tx_id": "0xfinal_tx_hash_here"
}
]
}
}
],
},
"owner_chain": "ethereum",
"owner_address": "0xowner",
"destination_address": "0xdestination"
}
```
### Block explorer link
It is conventional to show a resulting public block explorer page to a user that used their onchain wallet with an app. For EVM chains, like Ethereum and its L2s, [Blockscan explorers](https://blockscan.com/) make it simple to find the page using a transaction hash.
A user can be given a simple Blockscan URL after their payment completes onchain. Halliday developers can create this URL using the same payment status object and the transaction hash as seen above. Popular block explorer site information can be fetched from the Halliday API using the `GET /chains` endpoint.
The following is an example response from the chains endpoint.
```json theme={null}
{
"ethereum": {
"chain_id": 1,
"network": "ethereum",
"native_currency": {
"name": "Ether",
"symbol": "ETH",
"decimals": 18
},
"is_testnet": false,
"explorer": "https://etherscan.io/",
"image": "...",
"rpc": "..."
},
"arbitrum": {
"chain_id": 42161,
"network": "arbitrum",
"native_currency": {
"name": "Ether",
"symbol": "ETH",
"decimals": 18
},
"is_testnet": false,
"explorer": "https://arbiscan.io/",
"image": "...",
"rpc": "..."
}
}
```
Each block explorer URL can be found in its chain object in the response.
The URL for Blockscan pages (covers most EVM chains) is structured as follows.
```js theme={null}
`https://${origin_like_etherscan}/tx/${transaction_hash}`
```
The payment status object's fulfilled data will contain the name of the output chain which can then be used to select the explorer URL in the chains endpoint response.
This JavaScript code can be used to create the resulting transaction page URL for final step of a user's Halliday Payment.
```js theme={null}
async function getExplorerUrl(paymentId) {
// See Halliday API endpoints here https://docs.halliday.xyz/api-reference/
const halliday = 'https://v2.prod.halliday.xyz';
const options = {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY_HERE',
'Content-Type': 'application/json'
}
};
const chains = await (await fetch(`${halliday}/chains`, options)).json();
const payment = await (await fetch(`${halliday}/payments?payment_id=${paymentId}`, options)).json();
if (payment.status === 'COMPLETE') {
const r = payment.fulfilled.route;
const transactionHash = r[r.length-1].transaction_hash;
const outputChainName = payment.fulfilled.output_amount.asset.split(':')[0];
const explorerUrl = chains[outputChainName].explorer;
return `${explorerUrl}tx/${transactionHash}`;
}
}
```
# Compliance & Security
Source: https://docs.halliday.xyz/pages/compliance-security
More details on Halliday's compliance and security practices will be shown here soon.
# Frequently Asked Questions
Source: https://docs.halliday.xyz/pages/faq
#### What is Halliday Payments?
Halliday Payments is a non-custodial, unified crypto payment flow that can be implemented by developers using only 7 lines of code. It orchestrates three different product categories – onramps, centralized exchanges, and bridges – into a single app, simplifying the experience for users and allowing them to get their first dollar onchain in less than a minute.
#### How does it work?
Halliday Payments is built on the [Workflow Protocol](https://halliday.xyz/protocol), which is used to streamline blockchain development. Depending on the chain, traditional onramp providers may not be able to access liquidity, leaving users to figure out the best path on their own – a process that can take up to 30 minutes or more.
Halliday Payments simplifies this process by offering seamless interoperability across different blockchain networks, ensuring users can easily deposit without having to navigate across different platforms. Halliday uses a broad network of [integrations and support](https://halliday.xyz/ecosystem) to create interoperability no matter the starting token or geographic location, creating a truly Web2-like experience.
#### What is a one-time wallet (OTW)?
Each unique payment begins with a new onchain address called a one-time wallet (OTW), also known as the deposit address or a single-program wallet (SPW). Each onramp or swap will create a new OTW that is controlled only by the owner wallet address specified in the API call parameters. In the event a payment gets stuck or is funded after expiration, the owner address has the ability to sign transactions to recover the assets from the OTW or retry the payment.
End users control the OTW - Halliday never takes custody or control of user funds.
#### What can Halliday Payments be used for?
Halliday Payments is extremely flexible and can be utilized for a number of different business uses. This includes, but is not limited to:
* Blockchains: Simple deposit to a specific token, expanding access to liquidity
* Perps: Swap and stake automatically
* Prediction markets: Deposit and immediately buy
* Games: Easily purchase in-game assets
* Launchpads: Onboard and swap between coins
* Fintechs: On and offramp to stablecoins
The Workflow Protocol enables quick cross-chain development. Have an idea for a custom use case? Reach out to [partnerships@halliday.xyz](mailto:partnerships@halliday.xyz)
## How to use Halliday Payments
### Onramps
#### How can users onramp from fiat using Halliday Payments?
The onramp service is designed to integrate effortlessly with existing blockchain applications, providing fast, secure, and reliable onramping from fiat currencies.
To integrate the onramp service into an app, there are two main options:
* The Halliday Payments Widget: Easiest way to add a feature-complete UI for enabling onramps.
* The Halliday API: Completely controls the user experience using direct API integration.
Reach out to [partnerships@halliday.xyz](mailto:partnerships@halliday.xyz) to learn more and get access.
### Swaps
#### How can users pay with digital assets held in their own wallets?
The cross-chain swaps service is designed to integrate effortlessly with existing blockchain applications, providing fast, secure, and reliable swaps for digital asset holders.
To integrate the cross-chain swaps service into an app, there are two main options:
* The Halliday Payments Payments Widget: Easiest way to add a feature-complete UI for enabling swaps.
* The Halliday API: Completely controls the user experience using HTTP directly.
Reach out to [partnerships@halliday.xyz](mailto:partnerships@halliday.xyz) to learn more and get access.
#### What are the use cases for cross-chain swaps?
With cross-chain swaps, users can effortlessly bridge and swap into any token from any chain. Halliday routes between numerous onchain protocols for low latency and enhanced transaction speed.
This feature is ideal for users looking to acquire any token on any chain, or to consolidate their assets on a preferred network.
### Exchanges
#### How can users pay with assets from their exchange?
This feature is designed to integrate effortlessly with existing blockchain applications, providing fast, secure, and reliable onramping from their centralized exchange accounts.
To integrate this feature in an app, use the Halliday Payments Payments Widget.
#### What if the user does not have enough of the desired token in their exchange account to afford the transfer?
The exchange service supports two methods of supplementing the user's balance:
* The user can spend any 'buying power' available in the - exchange account to cover the difference.
* The user can use any payment methods they have saved in their exchange account to cover the difference.
### Offramps
#### How can my users offramp to fiat?
Halliday Payments is working on an offramp service designed to integrate effortlessly with existing apps, providing fast, secure, and reliable offramping to fiat currencies.
[Get in touch](https://halliday.xyz/contact) to learn more.
# Halliday API Documentation
Source: https://docs.halliday.xyz/pages/halliday-api-docs
The Halliday API provides direct HTTP access to payment services including onramps, swaps, exchange withdrawals, and offramps.
It offers complete control over the payment experience through a flexible, API-first integration.
There are two primary ways to integrate Halliday's payment services:
* [The Payments Widget](/pages/payments-sdk-docs): Pre-built UI component with complete payment functionality.
* [The Halliday API](/pages/halliday-api-docs): Direct API access for building custom payment experiences.
The Halliday API is ideal for developers who need:
* Full control over the user interface and experience
* Custom payment flows tailored to specific use cases
## Key Features
* **RESTful Design**: Standard HTTP methods and status codes
* **API-First Architecture**: No SDK dependencies required
* **Comprehensive Payment Support**: Onramps, swaps, offramps, and exchange balances
* **Real-time Status Tracking**: Monitor payments throughout their lifecycle
* **Flexible Integration**: Works with any programming language or framework
## Getting Started
To begin using the Halliday API:
1. **Obtain API Credentials**: [Get your API key from Halliday](https://halliday.xyz/contact)
2. **Explore the API**: Review the [API Quickstart](/pages/api-quickstart) guide
3. **Go Live**: Deploy to production users when ready
## API Endpoints
The Halliday API provides endpoints for:
* **Asset Discovery**: Find supported assets and payment routes
* **Quote Generation**: Get real-time pricing and optimal routing for payments
* **Payment Confirmation**: Confirm and initiate payments
* **Status Tracking**: Monitor payment progress
* **Fund Management**: Handle deposits and withdrawals
* **Payment History**: Find past payment executions with granularity
## Authentication
All API requests require authentication via API key:
```bash theme={null}
Authorization: Bearer pk_your-api-key-here
```
For detailed implementation examples, see the [API Quickstart](/pages/api-quickstart) guide. [Get your API key from Halliday here](https://halliday.xyz/contact).
## Method Docs and Playground
After getting an API key, check out the [Haliday Payments REST API endpoint method documentation](/api-reference/chains/get-supported-chains) tab.
This can also be found in the **API Reference** tab at the top of each page.
# Halliday Payments Documentation
Source: https://docs.halliday.xyz/pages/home
Welcome to the Halliday Payments documentation. Developers can explore our guides and examples to integrate a seamless crypto deposit flow in minutes.
Reach out and get your API keys now
Create your first app with the Payments SDK in minutes
See firsthand how to get started with the Halliday Payments SDK widget
Learn how to implement Halliday Payments with the API directly
## About Halliday Payments
With Halliday Payments, users can acquire **any token** on **any chain** with minimal effort. Seamlessly onramp from fiat, cross arbitrary bridges, and swap assets from an existing wallet (e.g. MetaMask), into whatever form. Connect with an exchange account (e.g. Coinbase) to transfer digital assets onto any chain.
Halliday Payments works in a fully self-custodial manner. Users can connect with any wallet to manage their funds. And, they remain in complete control, not Halliday.
Gain access to a unified integration that offers:
* Fiat on and off ramps
* Seamless cross-chain swaps
* Exchange payments direct to any network
An all-in-one payments solution that just works — purpose built for all chains, production ready on day one.
## Contact
To get in touch regarding integration of Halliday into an existing product, contact the team on X [@HallidayHQ](https://x.com/HallidayHQ) or reach out to [the business development team](mailto:partnerships@halliday.xyz).
Developers can join the conversation in the Halliday community [Discord](https://discord.halliday.xyz/) server. The Halliday team and community members can help with technical questions for those implementing payment automation.
## Use Cases
Examples of Web3 teams using Halliday Payments for the following use cases can be found on [the Ecosystem page](https://halliday.xyz/ecosystem).
### Fiat Onramp to All Chains
Users can onramp from fiat to a specific token on any chain with a streamlined experience. Fund an address using credit card or centralized exchange within a whitelabeled popup or embedded modal. A headless SDK is also available to developers.
### Cross-Chain Swaps
Halliday simplifies cross-chain token swaps for both developers and their end users. By design, the system finds the optimal route, from an input token on one chain, to an output token on another. No need to sign multiple transactions. One user interaction and Halliday handles the heavy lifting.
### Automated Workflows
Protocols can compose interactions across networks, like a purchase, a swap, a stake, and more with one interaction. Halliday handles cross-chain operations, retries, gas payments, asset balances, transaction formation, batch scheduling, and network routing all with a single click or tap.
## Which Tokens & Chains Does Halliday Payments Support?
Halliday can support **any token** on **any chain**, including routes through specific bridges or decentralized exchanges based on the needs of a use case.
**To support more onramps, tokens, chains, bridges, or DEXs, get in touch with the Halliday team today.**
# Export of the Documentation for LLMs
Source: https://docs.halliday.xyz/pages/llms-info
The llms.txt of the documentation can be crawled from here:
[https://docs.halliday.xyz/llms.txt](https://docs.halliday.xyz/llms.txt)
Additionally, a full export of all docs pages as markdown can be found here: [https://docs.halliday.xyz/llms-full.txt](https://docs.halliday.xyz/llms-full.txt)
The API specification `openapi.yaml` is here:
```yaml openapi.yaml theme={null}
openapi: 3.1.0
info:
title: Halliday API V2
description: |
API V2 for Halliday's payment infrastructure, supporting onramps, swaps, and offramps.
This API provides a unified interface for cryptocurrency payments, allowing developers to:
- Quote payments across multiple providers
- Execute payments with onramps, swaps, and offramps
- Track payment status and history
## Authentication
API key authentication is required for all endpoints.
version: 2.0.0
contact:
name: Contact Halliday
url: https://halliday.xyz
email: support@halliday.xyz
security:
- ApiKeyAuth: []
servers:
- url: https://v2.prod.halliday.xyz
description: Base domain
paths:
/chains:
get:
summary: Get supported chains
description: |
Get a list of all supported blockchain networks with their configuration details.
operationId: getChains
tags:
- Chains
responses:
"200":
description: List of supported chains
content:
application/json:
schema:
type: object
additionalProperties:
$ref: "#/components/schemas/ChainInfo"
example:
ethereum:
chain_id: 1
network: ethereum
native_currency:
name: Ether
symbol: ETH
decimals: 18
is_testnet: false
explorer: https://etherscan.io/
image: ...
rpc: ...
arbitrum:
chain_id: 42161
network: arbitrum
native_currency:
name: Ether
symbol: ETH
decimals: 18
is_testnet: false
explorer: https://arbiscan.io/
image: ...
rpc: ...
"401":
description: Unauthorized
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
errors:
- kind: other
message: "Invalid API key"
"403":
description: Forbidden
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
errors:
- kind: other
message: "Insufficient permissions to access this resource"
"500":
description: Internal server error
content:
application/json:
schema:
$ref: "#/components/schemas/ServerErrorResponse"
example:
errors:
- message: "A server error occurred. Please try again later."
/assets:
get:
summary: Get asset details
description: |
Get detailed information about specific assets including metadata, symbols, and chain information.
operationId: getAssets
tags:
- Assets
parameters:
- name: assets[]
in: query
required: false
description: List of assets to get details for
style: form
explode: true
schema:
type: array
items:
$ref: "#/components/schemas/Asset"
responses:
"200":
description: Asset details
content:
application/json:
schema:
type: object
additionalProperties:
$ref: "#/components/schemas/AssetDetails"
example:
usd:
issuer: USA
name: United States Dollar
symbol: USD
decimals: 2
eur:
issuer: ECB
name: Euro
symbol: EUR
decimals: 2
"ethereum:0x":
chain: ethereum
address: "0x"
name: Ether
symbol: ETH
decimals: 18
image_url: https://coin-images.coingecko.com/coins/images/279/large/ethereum.png?1696501628
"400":
description: Bad request
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
errors:
- kind: other
message: "Invalid asset format. Expected format: 'chain:address' for tokens or currency code for fiats"
"401":
description: Unauthorized
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
errors:
- kind: other
message: "Missing or invalid API key"
"403":
description: Forbidden
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
errors:
- kind: other
message: "API key does not have permission to access asset information"
"500":
description: Internal server error
content:
application/json:
schema:
$ref: "#/components/schemas/ServerErrorResponse"
example:
errors:
- message: "A server error occurred. Please try again later."
/assets/available-inputs:
get:
summary: Get available input assets for a given output asset
description: |
Get a list of assets that can be used as inputs for the given output assets and providers.
operationId: getInputAssets
tags:
- Assets
parameters:
- name: inputs[]
in: query
required: false
description: Filter by specific input assets
style: form
explode: true
schema:
type: array
items:
$ref: "#/components/schemas/Asset"
- name: outputs[]
in: query
required: false
description: Output assets to find inputs for
style: form
explode: true
schema:
type: array
items:
$ref: "#/components/schemas/Asset"
- name: onramps[]
in: query
required: false
description: Filter by specific onramp providers
style: form
explode: true
schema:
type: array
items:
type: string
- name: offramps[]
in: query
required: false
description: Filter by specific offramp providers
style: form
explode: true
schema:
type: array
items:
type: string
responses:
"200":
description: Available input assets grouped by output
content:
application/json:
schema:
type: object
additionalProperties:
type: object
properties:
fiats:
type: array
items:
$ref: "#/components/schemas/Fiat"
tokens:
type: array
items:
$ref: "#/components/schemas/Token"
required:
- fiats
- tokens
example:
"ethereum:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48":
fiats:
- usd
tokens: []
"400":
description: Bad request
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
errors:
- kind: other
message: "Invalid provider specified. Provider 'invalid_provider' is not supported"
"401":
description: Unauthorized
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
errors:
- kind: other
message: "Authentication required. Please provide a valid API key."
"403":
description: Forbidden
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
errors:
- kind: other
message: "Access denied. Your API key does not have permission to query available assets."
"500":
description: Internal server error
content:
application/json:
schema:
$ref: "#/components/schemas/ServerErrorResponse"
example:
errors:
- message: "A server error occurred. Please try again later."
/assets/available-outputs:
get:
summary: Get available output assets for a given input asset
description: |
Get a list of assets that can be received as outputs for the given input assets and providers.
operationId: getOutputAssets
tags:
- Assets
parameters:
- name: inputs[]
in: query
required: false
description: Input assets to find outputs for
style: form
explode: true
schema:
type: array
items:
$ref: "#/components/schemas/Asset"
- name: outputs[]
in: query
required: false
description: Filter by specific output assets
style: form
explode: true
schema:
type: array
items:
$ref: "#/components/schemas/Asset"
- name: onramps[]
in: query
required: false
description: Filter by specific onramp providers
style: form
explode: true
schema:
type: array
items:
type: string
- name: offramps[]
in: query
required: false
description: Filter by specific offramp providers
style: form
explode: true
schema:
type: array
items:
type: string
responses:
"200":
description: Available output assets grouped by input
content:
application/json:
schema:
type: object
additionalProperties:
type: object
properties:
fiats:
type: array
items:
$ref: "#/components/schemas/Fiat"
tokens:
type: array
items:
$ref: "#/components/schemas/Token"
required:
- fiats
- tokens
example:
"usd":
fiats: []
tokens:
- "arbitrum:0x"
- "ethereum:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
"400":
description: Bad request
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
errors:
- kind: other
message: "Invalid input asset format. Asset 'invalid-format' does not match expected pattern."
"401":
description: Unauthorized
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
errors:
- kind: other
message: "Invalid or expired API key"
"403":
description: Forbidden
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
errors:
- kind: other
message: "Your account does not have access to this endpoint"
"500":
description: Internal server error
content:
application/json:
schema:
$ref: "#/components/schemas/ServerErrorResponse"
example:
errors:
- message: "A server error occurred. Please try again later."
/payments:
get:
summary: Get payment status
description: |
Get the current status of a payment, including progress through onramp/swap/offramp stages.
operationId: getPaymentStatus
tags:
- Payments
parameters:
- name: payment_id
in: query
required: true
description: Payment identifier to check
schema:
type: string
responses:
"200":
description: Payment status retrieved successfully!
content:
application/json:
schema:
$ref: "#/components/schemas/PaymentStatus"
"400":
description: Bad request
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
errors:
- kind: other
message: "Missing required parameter: payment_id"
"401":
description: Unauthorized
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
errors:
- kind: other
message: "Invalid API key"
"403":
description: Forbidden
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
errors:
- kind: other
message: "You do not have permission to view this payment"
"404":
description: Payment not found
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
errors:
- kind: other
message: "Payment with payment_id 'pay_abc123' not found"
"500":
description: Internal server error
content:
application/json:
schema:
$ref: "#/components/schemas/ServerErrorResponse"
example:
errors:
- message: "A server error occurred. Please try again later."
/payments/history:
get:
summary: Get payment history
description: |
Retrieve a paginated list of payment statuses filtered by owner.
Returns an array of payment status objects and a pagination key for fetching the next page.
operationId: getPaymentHistory
tags:
- Payments
parameters:
- name: owner_address
in: query
required: true
description: Owner address to filter payments by
schema:
type: string
- name: pagination_key
in: query
required: false
description: Pagination key from previous response to fetch next page
schema:
type: string
- name: limit
in: query
required: false
description: Maximum number of results to return
schema:
type: number
- name: category
in: query
required: false
description: Filter payments by category
schema:
type: string
enum: [ALL, NEW_OR_FUNDED]
default: NEW_OR_FUNDED
responses:
"200":
description: Payment history retrieved successfully
content:
application/json:
schema:
type: object
required:
- payment_statuses
properties:
payment_statuses:
type: array
items:
$ref: "#/components/schemas/PaymentStatus"
description: Array of payment status objects
next_pagination_key:
type: string
description: Pagination key to fetch the next page of results
example: "42ff9b02-037f-4a90-87fb-a4d2ca128565_2025-11-12T22:08:52.517Z"
example:
payment_statuses:
- payment_id: ""
status: "COMPLETE"
funded: true
created_at: "2025-11-12T20:15:30.000Z"
updated_at: "2025-11-12T20:25:45.000Z"
initiate_fund_by: "2025-11-12T21:15:30.000Z"
completed_at: "2025-11-12T20:25:45.000Z"
quoted:
output_amount:
asset: "ethereum:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
amount: "100.00"
fees:
total_fees: "3.50"
conversion_fees: "2.00"
network_fees: "1.00"
business_fees: "0.50"
currency_symbol: "USD"
onramp: "stripe"
onramp_method: "credit_card"
route:
- type: "ONRAMP"
net_effect:
consume:
- account: "USER"
resource:
asset: "usd"
property: "BALANCE"
amount:
amount: "103.50"
produce:
- account: "PROCESSING_ADDRESS"
resource:
asset: "ethereum:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
property: "BALANCE"
amount:
amount: "100.00"
pieces_info:
- type: "onramp"
step_index: 0
fulfilled:
output_amount:
asset: "ethereum:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
amount: "100.00"
fees:
total_fees: "3.50"
conversion_fees: "2.00"
network_fees: "1.00"
business_fees: "0.50"
currency_symbol: "USD"
onramp: "stripe"
onramp_method: "credit_card"
route:
- status: "COMPLETE"
type: "ONRAMP"
net_effect:
consume:
- account: "USER"
resource:
asset: "usd"
property: "BALANCE"
amount:
amount: "103.50"
produce:
- account: "PROCESSING_ADDRESS"
resource:
asset: "ethereum:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
property: "BALANCE"
amount:
amount: "100.00"
pieces_info:
- type: "onramp"
step_index: 0
transaction_hash: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
current_prices:
"USD": "1.00"
"ethereum:0x": "4200.00"
"ethereum:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "1.00"
price_currency: "USD"
processing_addresses:
- chain: "ethereum"
address: "0xaddress..."
owner_address: "0xowner..."
destination_address: "0xdest..."
next_pagination_key: "42ff9b02-037f-4a90-87fb-a4d2ca128565_2025-11-12T22:08:52.517Z"
"400":
description: Bad request
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
errors:
- kind: other
message: "Missing required parameter: owner_address"
"401":
description: Unauthorized
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
errors:
- kind: other
message: "Invalid API key"
"403":
description: Forbidden
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
errors:
- kind: other
message: "You do not have permission to access payment history for this address"
"500":
description: Internal server error
content:
application/json:
schema:
$ref: "#/components/schemas/ServerErrorResponse"
example:
errors:
- message: "A server error occurred. Please try again later."
/payments/quotes:
post:
summary: Get payment quotes
description: |
Request quotes for payments, supporting both fixed input and fixed output scenarios.
Returns multiple quote options with pricing, fees, and routing information.
This endpoint can also be used to requote from an existing payment by providing a payment_id.
When requoting, input amounts are automatically derived from
the payment's current state, but you can optionally override the output asset.
operationId: getPaymentQuotes
tags:
- Payments
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/QuoteRequest"
responses:
"200":
description: Successful quote response
content:
application/json:
schema:
$ref: "#/components/schemas/QuoteResponse"
"400":
description: Bad request
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
errors:
- kind: amount
given:
amount: "5"
limits:
min:
amount: "10"
max:
amount: "10000"
source: "moonpay"
message: "Amount too low. Minimum amount is $10 USD"
"401":
description: Unauthorized
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
errors:
- kind: other
message: "API key missing or invalid"
"403":
description: Forbidden
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
errors:
- kind: other
message: "Your API key does not have permission to create quotes"
"500":
description: Internal server error
content:
application/json:
schema:
$ref: "#/components/schemas/ServerErrorResponse"
example:
errors:
- message: "A server error occurred. Please try again later."
/payments/confirm:
post:
summary: Confirm a payment
description: |
Confirm a previously quoted payment and receive deposit instructions.
Returns information on how to fund the payment (widget URL, contract address, etc.).
operationId: confirmPayment
tags:
- Payments
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- payment_id
- state_token
- owner_address
- destination_address
properties:
payment_id:
type: string
description: ID of the payment from the quote to confirm
state_token:
type: string
format: byte
description: State token from the quote response
owner_address:
type: string
description: Owner address for the payment. This is the address that the payment will be sent from.
destination_address:
type: string
description: Destination address for the payment. This is the address that the payment will be sent to.
client_redirect_url:
type: string
format: uri
description: Optional URL to redirect users to after an onramp flow completes.
responses:
"200":
description: Payment confirmed successfully
content:
application/json:
schema:
$ref: "#/components/schemas/PaymentStatus"
"400":
description: Bad request
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
errors:
- kind: other
message: "Invalid state_token. The quote may have expired."
"401":
description: Unauthorized
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
errors:
- kind: other
message: "Authentication failed. Invalid API key."
"403":
description: Forbidden
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
errors:
- kind: other
message: "Access denied. This payment belongs to a different account."
"404":
description: Payment not found
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
errors:
- kind: other
message: "Payment 'pay_xyz789' not found or has expired"
"500":
description: Internal server error
content:
application/json:
schema:
$ref: "#/components/schemas/ServerErrorResponse"
example:
errors:
- message: "A server error occurred. Please try again later."
/payments/withdraw:
post:
summary: Return funds to owner or retry payment
description: |
Request a withdrawal to return funds back to the owner or to a requoted processing address.
This endpoint should be used when a payment cannot be completed and funds need to be returned.
operationId: withdrawPayment
tags:
- Payments
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/WithdrawPaymentAuthorizationRequest"
responses:
"200":
description: Withdrawal initiated successfully
content:
application/json:
schema:
$ref: "#/components/schemas/WithdrawPaymentAuthorizationResponse"
"404":
description: Payment not found
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
errors:
- kind: other
message: "Payment 'pay_def456' not found"
"400":
description: Bad request
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
errors:
- kind: other
message: "Missing field 'token_amounts'."
"401":
description: Unauthorized
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
errors:
- kind: other
message: "Invalid or missing API key"
"403":
description: Forbidden
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
errors:
- kind: other
message: "You are not authorized to withdraw funds from this payment"
"500":
description: Internal server error
content:
application/json:
schema:
$ref: "#/components/schemas/ServerErrorResponse"
example:
errors:
- message: "A server error occurred. Please try again later."
/payments/withdraw/confirm:
post:
summary: Confirm withdrawal request
description: |
Submit and execute the signed withdrawal request to return funds back to the owner or to a requoted processing address.
operationId: withdrawPaymentConfirm
tags:
- Payments
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/WithdrawPaymentRequest"
responses:
"200":
description: Withdrawal executed successfully
content:
application/json:
schema:
$ref: "#/components/schemas/WithdrawPaymentResponse"
"400":
description: Bad request
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
errors:
- kind: other
message: "Invalid request body"
"401":
description: Unauthorized
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
errors:
- kind: other
message: "Invalid API key"
"403":
description: Forbidden
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
errors:
- kind: other
message: "You are not authorized to withdraw funds from this payment"
"500":
description: Internal server error
content:
application/json:
schema:
$ref: "#/components/schemas/ServerErrorResponse"
example:
errors:
- message: "A server error occurred. Please try again later."
/payments/balances:
post:
summary: Get wallet balances
description: |
Retrieve balances for the wallets associated with a payment. You can optionally supply additional
wallet and token pairs to check alongside the payment's primary wallet.
operationId: getPaymentBalances
tags:
- Payments
requestBody:
required: false
content:
application/json:
schema:
$ref: "#/components/schemas/RequestForPaymentBalances"
example:
payment_id: pay_abc123
custom_queries:
- address: "0xabc1234567890abcdef1234567890abcdef1234"
token: "ethereum:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
- address: "0xdef1234567890abcdef1234567890abcdef5678"
token: "arbitrum:0xfffFFFFFfFFfffFFfFffffFffFFfFFfFFfFFf"
responses:
"200":
description: Wallet balances retrieved successfully
content:
application/json:
schema:
$ref: "#/components/schemas/PaymentBalancesResponse"
"400":
description: Bad request
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
errors:
- kind: other
message: "Invalid wallet address format"
"401":
description: Unauthorized
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
errors:
- kind: other
message: "Invalid API key"
"403":
description: Forbidden
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
errors:
- kind: other
message: "You do not have permission to query payment balances"
"404":
description: Payment not found
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
errors:
- kind: other
message: "Payment 'pay_abc123' not found"
"500":
description: Internal server error
content:
application/json:
schema:
$ref: "#/components/schemas/ServerErrorResponse"
example:
errors:
- message: "A server error occurred. Please try again later."
components:
securitySchemes:
ApiKeyAuth:
type: http
scheme: bearer
bearerFormat: API_KEY
schemas:
# Core Data Types
ChainInfo:
type: object
required:
- chain_id
- network
- native_currency
- is_testnet
properties:
chain_id:
type: number
description: Chain ID for the network
example: 1
network:
type: string
description: Network name
example: ethereum
native_currency:
type: object
properties:
name:
type: string
example: Ether
symbol:
type: string
example: ETH
decimals:
type: number
example: 18
required:
- name
- symbol
- decimals
is_testnet:
type: boolean
description: Whether this is a testnet
example: false
explorer:
type: string
format: uri
description: Block explorer URL
example: https://etherscan.io
image:
type: string
format: uri
description: Chain logo URL
rpc:
type: string
format: uri
description: RPC endpoint URL
AssetDetails:
oneOf:
- $ref: "#/components/schemas/FiatDetails"
- $ref: "#/components/schemas/TokenDetails"
FiatDetails:
type: object
required:
- issuer
- name
- symbol
- decimals
properties:
issuer:
type: string
description: Issuer of the fiat
example: USA
name:
type: string
description: Full currency name
example: US Dollar
symbol:
type: string
description: Currency symbol
example: USD
decimals:
type: number
minimum: 0
maximum: 18
description: Number of decimal places
example: 2
alias:
type: string
description: Alternative symbol or alias
image_url:
type: string
format: uri
description: Currency icon URL
TokenDetails:
type: object
required:
- chain
- address
- name
- symbol
- decimals
properties:
chain:
type: string
description: Blockchain network
example: ethereum
address:
type: string
description: Token contract address
example: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
name:
type: string
description: Token name
example: USD Coin
symbol:
type: string
description: Token symbol
example: USDC
decimals:
type: number
minimum: 0
maximum: 18
description: Token decimals
example: 6
alias:
type: string
description: Alternative symbol or alias
image_url:
type: string
format: uri
description: Token icon URL
is_native:
type: boolean
description: Whether this is the native token
example: false
wrapper:
type: string
description: Wrapper token symbol
Asset:
type: string
description: Identifier in the token format ("chain:address") or fiat currency code ("USD")
example: ethereum:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
Fiat:
type: string
description: Fiat currency code
example: USD
Token:
type: string
description: Token identifier in the format "chain:address"
pattern: "^[a-z]+:0x[a-fA-F0-9]+$"
example: "ethereum:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
AssetAmount:
type: object
required:
- asset
- amount
properties:
asset:
$ref: "#/components/schemas/Asset"
amount:
type: string
description: Amount as a string to preserve precision
example: "1"
TokenAmount:
type: object
required:
- token
- amount
properties:
token:
$ref: "#/components/schemas/Token"
amount:
type: string
description: Amount of the token to withdraw, represented as a string to preserve precision
# Quote Request Types
QuoteRequest:
type: object
required:
- request
- price_currency
properties:
request:
$ref: "#/components/schemas/FixedInputQuoteRequest"
price_currency:
type: string
description: Currency that all prices are denominated in
example: USD
onramps:
type: array
items:
type: string
description: Filter by specific onramp providers
example: ["moonpay", "coinbase"]
onramp_methods:
type: array
items:
type: string
description: Filter by onramp payment methods
example: ["credit_card", "ach", "apple_pay"]
customer_ip_address:
type: string
description: IP address of the customer
customer_id:
type: string
description: Customer ID for tracking
customer_geolocation:
type: object
description: Geolocation information
required:
- alpha3_country_code
properties:
alpha3_country_code:
type: string
description: Three-letter ISO country code
state_code:
type: string
description: State or region code when applicable. Use an empty string when not applicable.
parent_payment_id:
type: string
description: Optional parent payment identifier used when creating a quote from an existing payment.
FixedInputQuoteRequest:
type: object
required:
- kind
- fixed_input_amount
- output_asset
properties:
kind:
type: string
enum: [FIXED_INPUT]
fixed_input_amount:
$ref: "#/components/schemas/AssetAmount"
output_asset:
$ref: "#/components/schemas/Asset"
# Quote Response Types
QuoteResponse:
type: object
required:
- quote_request
- quotes
- current_prices
- price_currency
- state_token
- quoted_at
- accept_by
- failures
properties:
quote_request:
$ref: "#/components/schemas/QuoteRequest"
quotes:
type: array
items:
$ref: "#/components/schemas/Quote"
current_prices:
type: object
additionalProperties:
type: string
description: Mapping of asset symbols to their unit prices
example:
"USD": "1.00"
"ethereum:0x": "4200"
"ethereum:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "1.00"
price_currency:
type: string
description: Currency that all prices are denominated in
example: USD
state_token:
type: string
description: |
Signed state token containing routing and payment flow information.
This value must be passed unmodified in subsequent API calls.
Do not attempt to parse or modify this data as it is cryptographically signed.
quoted_at:
type: string
format: date-time
description: |
Timestamp of when the quote was created, in UTC ISO 8601 format
accept_by:
type: string
format: date-time
description: |
Timestamp of when the quote will expire, in UTC ISO 8601 format
failures:
type: array
description: Providers that failed to return a quote.
items:
$ref: "#/components/schemas/FailureContent"
QuotedEntry:
type: object
required:
- output_amount
- fees
- route
properties:
output_amount:
$ref: "#/components/schemas/AssetAmount"
fees:
$ref: "#/components/schemas/Fees"
onramp:
type: string
description: Onramp provider name
onramp_method:
type: string
description: Payment method
example: credit_card
route:
type: array
items:
$ref: "#/components/schemas/QuotedRouteItem"
description: Quoted workflow steps and their effects
Quote:
allOf:
- $ref: "#/components/schemas/QuotedEntry"
- type: object
properties:
payment_id:
type: string
description: Unique payment identifier
required:
- payment_id
FailureContent:
type: object
required:
- service_ids
- latency_seconds
properties:
service_ids:
type: array
description: Providers that failed to return quotes.
items:
type: string
latency_seconds:
type: number
format: float
description: Time taken for the failed quote attempt.
issues:
type: array
description: Optional list of structured issues describing the failure.
items:
$ref: "#/components/schemas/Issue"
FulfilledEntry:
type: object
required:
- route
properties:
output_amount:
$ref: "#/components/schemas/AssetAmount"
fees:
$ref: "#/components/schemas/Fees"
onramp:
type: string
description: Onramp provider name
onramp_method:
type: string
description: Payment method
example: credit_card
route:
type: array
items:
$ref: "#/components/schemas/FulfilledRouteItem"
description: In-progress workflow steps, statuses, and their effects
# Fees
Fees:
type: object
required:
- total_fees
- conversion_fees
- network_fees
- business_fees
- currency_symbol
properties:
total_fees:
type: string
description: Total fees amount
conversion_fees:
type: string
description: Ramps + bridge + DEX fees
network_fees:
type: string
description: Blockchain gas fees
business_fees:
type: string
description: Developer integration fees
currency_symbol:
type: string
description: Currency symbol that the fees are denominated in (e.g., "USD", "USDC")
example: "USD"
# Confirm Payment Response
ConfirmPaymentResponse:
type: object
required:
- quote_request
- quote
properties:
quote:
$ref: "#/components/schemas/Quote"
current_prices:
type: object
additionalProperties:
type: string
description: Mapping of asset symbols to their unit prices
example:
"USD": "1.00"
"ethereum:0x": "4200"
"ethereum:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "1.00"
price_currency:
type: string
description: Currency that all prices are denominated in
example: USD
Instruction:
oneOf:
- $ref: "#/components/schemas/OnrampWidgetInstruction"
- $ref: "#/components/schemas/SwapInstruction"
- $ref: "#/components/schemas/ErrorWithdrawOrRolloverInstruction"
discriminator:
propertyName: instruction_type
OnrampWidgetInstruction:
type: object
required:
- instruction_type
- payment_id
- onramp_url
- deposit_info
- onramp_currency
properties:
instruction_type:
type: string
enum: [ONRAMP_WIDGET]
onramp_url:
type: string
description: URL to query to get onramp details and redirect to onramp
example: "https://app.halliday.xyz/funding?payment_id=pay123&..."
payment_id:
type: string
description: ID of the payment to onramp
deposit_info:
type: object
required:
- deposit_token
- deposit_amount
- deposit_address
- deposit_chain
properties:
deposit_token:
$ref: "#/components/schemas/Token"
description: Token required for deposit
deposit_amount:
type: string
description: Amount of the token required for deposit
example: "100.50"
deposit_address:
type: string
description: Contract address for payment
deposit_chain:
type: string
description: Blockchain for payment contract
example: ethereum
onramp_currency:
$ref: "#/components/schemas/Fiat"
SwapInstruction:
type: object
required:
- instruction_type
- deposit_info
properties:
instruction_type:
type: string
enum: [SWAP]
deposit_info:
type: object
required:
- deposit_token
- deposit_amount
- deposit_address
- deposit_chain
properties:
deposit_token:
$ref: "#/components/schemas/Token"
description: Token required for deposit
deposit_amount:
type: string
description: Amount of the token required for deposit
example: "100.50"
deposit_address:
type: string
description: Contract address for payment
deposit_chain:
type: string
description: Blockchain for payment contract
example: ethereum
ErrorWithdrawOrRolloverInstruction:
type: object
required:
- instruction_type
- error_message
- asset_amounts
properties:
instruction_type:
type: string
enum: [ERROR_WITHDRAW_OR_ROLLOVER]
description: Instruction type for error handling with withdrawal or rollover option
error_message:
type: string
description: Brief explanation of the error.
asset_amounts:
type: array
items:
$ref: "#/components/schemas/AssetAmount"
parent_payment_id:
type: string
description: ID of the parent payment where the funds are being rolled over from.
description: |
When an error occurs during payment processing, this instruction provides:
1. Information about the error
2. The assets and amounts to withdraw or rollover
3. For rollovers, the parent payment ID of the payment that encountered the error
4. To retry the payment, call /payments/quotes with the payment_id to get new quote options.
BalanceQuery:
type: object
required:
- address
- token
properties:
address:
type: string
description: One-time wallet address associated with the payment.
example: "0xabc1234567890abcdef1234567890abcdef1234"
token:
$ref: "#/components/schemas/Token"
description: Token identifier to query in the wallet.
example: "ethereum:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
RequestForBalances:
type: object
properties:
workflow_id:
type: string
description: Payment identifier whose one-time wallet balances should be returned.
example: pay_abc123
custom_queries:
type: array
description: Additional wallet and token combinations to query.
items:
$ref: "#/components/schemas/BalanceQuery"
BalanceResult:
type: object
required:
- address
- token
- value
properties:
address:
type: string
description: One-time wallet address that was queried.
example: "0xabc1234567890abcdef1234567890abcdef1234"
token:
$ref: "#/components/schemas/Token"
description: Token identifier that was queried.
example: "ethereum:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
value:
oneOf:
- type: object
required:
- kind
- amount
properties:
kind:
type: string
enum: [amount]
amount:
$ref: "#/components/schemas/Amount"
- type: object
required:
- kind
properties:
kind:
type: string
enum: [error]
discriminator:
propertyName: kind
BalancesResponse:
type: object
required:
- balance_results
properties:
balance_results:
type: array
items:
$ref: "#/components/schemas/BalanceResult"
RequestForPaymentBalances:
type: object
properties:
payment_id:
type: string
description: Payment identifier whose associated wallets should be queried.
example: pay_abc123
custom_queries:
type: array
description: Additional wallet and token combinations to query.
items:
type: object
required:
- address
- token
properties:
address:
type: string
description: Wallet address to query.
example: "0xabc1234567890abcdef1234567890abcdef1234"
token:
$ref: "#/components/schemas/Token"
description: Token identifier to query at the wallet address.
example: "ethereum:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
PaymentBalancesResult:
type: object
required:
- address
- token
- value
properties:
address:
type: string
description: Wallet address that was queried.
example: "0xabc1234567890abcdef1234567890abcdef1234"
token:
$ref: "#/components/schemas/Token"
description: Token identifier that was queried.
example: "ethereum:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
value:
oneOf:
- type: object
required:
- kind
- amount
properties:
kind:
type: string
enum: [amount]
amount:
$ref: "#/components/schemas/Amount"
- type: object
required:
- kind
properties:
kind:
type: string
enum: [error]
discriminator:
propertyName: kind
PaymentBalancesResponse:
type: object
required:
- balance_results
properties:
balance_results:
type: array
items:
$ref: "#/components/schemas/PaymentBalancesResult"
# Withdrawal
WithdrawPaymentAuthorizationRequest:
type: object
required:
- payment_id
- token_amounts
- recipient_address
properties:
payment_id:
type: string
description: ID of the payment to withdraw
token_amounts:
type: array
items:
$ref: "#/components/schemas/TokenAmount"
description: List of token amounts to withdraw back to the owner or to a requoted processing address
recipient_address:
type: string
description: On-chain address to withdraw the funds to. The assets will go to the address on the same chain the assets are on.
WithdrawPaymentAuthorizationResponse:
type: object
required:
- payment_id
- withdraw_authorization
properties:
payment_id:
type: string
description: ID of the withdrawal transaction
withdraw_authorization:
type: string
description: An EIP-712 JSON string that the owner signs to perform a withdrawal on their one-time payment wallet
WithdrawPaymentRequest:
type: object
required:
- payment_id
- token_amounts
- recipient_address
- owner_signature
properties:
payment_id:
type: string
description: ID of the payment to withdraw
token_amounts:
type: array
items:
$ref: "#/components/schemas/TokenAmount"
description: List of token amounts to withdraw back to the owner or to a requoted processing address
recipient_address:
type: string
description: On-chain address to withdraw the funds to, on the same chain as the assets to withdraw.
owner_signature:
type: string
description: The payment owner's signature on the withdrawal_authorization, authorizing the withdrawal
WithdrawPaymentResponse:
type: object
required:
- payment_id
- transaction_hash
properties:
payment_id:
type: string
description: ID of the payment to withdraw
transaction_hash:
type: string
description: Blockchain transaction hash for the withdrawal
# Payment Status
PaymentStatus:
type: object
required:
- payment_id
- status
- funded
- created_at
- updated_at
- initiate_fund_by
- quoted
- fulfilled
- current_prices
- price_currency
- processing_addresses
- owner_address
- destination_address
properties:
payment_id:
type: string
status:
type: string
enum: [PENDING, COMPLETE, FAILED, EXPIRED, WITHDRAWN, TAINTED]
funded:
type: boolean
description: Whether the payment has been funded
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
initiate_fund_by:
type: string
format: date-time
description: Deadline for funding the payment.
completed_at:
type: string
format: date-time
quote_request:
$ref: "#/components/schemas/QuoteRequest"
description: Optional reference to the original quote request.
quoted:
$ref: "#/components/schemas/QuotedEntry"
fulfilled:
$ref: "#/components/schemas/FulfilledEntry"
current_prices:
type: object
additionalProperties:
type: string
description: Mapping of asset symbols to their unit prices
example:
"USD": "1.00"
"ethereum:0x": "4200"
"ethereum:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "1.00"
price_currency:
type: string
description: Currency that all prices are denominated in
example: USD
customer_id:
type: string
description: ID of the customer who created the payment
processing_addresses:
type: array
items:
type: object
required:
- chain
- address
properties:
chain:
type: string
description: Blockchain network
example: ethereum
address:
type: string
description: Payment processing contract address
owner_address:
type: string
description: Address of the owner of the payment
destination_address:
type: string
description: Address of the destination of the payment
next_instruction:
$ref: "#/components/schemas/NextInstruction"
nullable: true
PaymentStatusRequest:
type: object
required:
- payment_id
properties:
payment_id:
type: string
description: Unique payment identifier to fetch status for.
NextInstruction:
description: Instruction payload that returns the funding pages if the payment requires funding.
oneOf:
- $ref: "#/components/schemas/OnrampOrTransferInInstruction"
discriminator:
propertyName: type
mapping:
ONRAMP: "#/components/schemas/OnrampOrTransferInInstruction"
example:
type: ONRAMP
payment_id:
funding_page_url: https://app.halliday.xyz/funding/${payment_id}
deposit_info:
- deposit_token: base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913
deposit_amount: "4.8"
deposit_address: 0xaddress
deposit_chain: base
OnrampOrTransferInInstruction:
type: object
required:
- type
- payment_id
- funding_page_url
- deposit_info
properties:
type:
type: string
enum: [ONRAMP, TRANSFER_IN]
description: Discriminator for this instruction type.
payment_id:
type: string
description: The payment this instruction is associated with.
funding_page_url:
type: string
format: uri
description: URL where the user can complete funding for this payment.
deposit_info:
type: array
description: One or more deposit routes to fund the payment.
items:
$ref: "#/components/schemas/DepositInfo"
DepositInfo:
type: object
required:
- deposit_token
- deposit_amount
- deposit_address
- deposit_chain
properties:
deposit_token:
# Use your existing Asset schema (identifier like "chain:address" or fiat code).
$ref: "#/components/schemas/Asset"
deposit_amount:
type: string
description: Amount to deposit, as a decimal string.
example: "4.8"
deposit_address:
type: string
description: Address to which funds will be deposited.
deposit_chain:
type: string
description: Network on which the deposit will be made (e.g., base, ethereum).
example: base
# Error Responses
Issue:
oneOf:
- $ref: "#/components/schemas/AmountIssue"
- $ref: "#/components/schemas/AmountDownstreamIssue"
- $ref: "#/components/schemas/GeolocationIssue"
- $ref: "#/components/schemas/ProviderIssue"
- $ref: "#/components/schemas/PayinMethodIssue"
- $ref: "#/components/schemas/OtherIssue"
- $ref: "#/components/schemas/UnknownIssue"
discriminator:
propertyName: kind
AmountIssue:
type: object
required:
- kind
- given
- limits
- source
- message
properties:
kind:
type: string
enum: [amount]
given:
$ref: "#/components/schemas/Amount"
limits:
$ref: "#/components/schemas/Limits"
source:
type: string
message:
type: string
AmountDownstreamIssue:
type: object
required:
- kind
- given
- limits
- source
- message
properties:
kind:
type: string
enum: [amount-downstream]
given:
$ref: "#/components/schemas/Amount"
limits:
$ref: "#/components/schemas/Limits"
source:
type: string
message:
type: string
GeolocationIssue:
type: object
required:
- kind
- message
properties:
kind:
type: string
enum: [geolocation]
message:
type: string
ProviderIssue:
type: object
required:
- kind
- message
properties:
kind:
type: string
enum: [provider]
message:
type: string
PayinMethodIssue:
type: object
required:
- kind
- message
properties:
kind:
type: string
enum: [payin_method]
message:
type: string
OtherIssue:
type: object
required:
- kind
- message
properties:
kind:
type: string
enum: [other]
message:
type: string
UnknownIssue:
type: object
required:
- kind
- message
properties:
kind:
type: string
enum: [unknown]
message:
type: string
Amount:
type: object
required:
- amount
properties:
amount:
type: string
Limits:
type: object
properties:
min:
$ref: "#/components/schemas/Amount"
max:
$ref: "#/components/schemas/Amount"
ErrorResponse:
type: object
required:
- errors
properties:
errors:
type: array
items:
$ref: "#/components/schemas/Issue"
description: Error response for known errors
ServerErrorResponse:
type: object
required:
- errors
properties:
errors:
type: array
items:
type: object
required:
- message
properties:
message:
type: string
minItems: 1
maxItems: 1
description: Error response for unkown server errors
# Workflow Details
QuotedRouteItem:
type: object
required:
- type
- net_effect
- pieces_info
properties:
type:
type: string
enum: [ONRAMP, ONCHAIN_STEP, USER_FUND]
net_effect:
$ref: "#/components/schemas/EffectAmount"
pieces_info:
type: array
items:
$ref: "#/components/schemas/PieceInfo"
step_index:
type: number
FulfilledRouteItem:
type: object
required:
- status
- type
- net_effect
- pieces_info
properties:
status:
type: string
enum: [PENDING, COMPLETE, UNREACHABLE, FAILED]
type:
type: string
enum: [ONRAMP, ONCHAIN_STEP, USER_FUND]
net_effect:
$ref: "#/components/schemas/EffectAmount"
pieces_info:
type: array
items:
$ref: "#/components/schemas/PieceInfo"
step_index:
type: number
transaction_hash:
type: string
description: Blockchain transaction hash for the step
PieceInfo:
type: object
required:
- type
properties:
type:
type: string
enum: [onramp, poll, bridge, transfer_out, rev_share, convert]
Effect:
type: object
required:
- consume
- produce
properties:
consume:
type: array
items:
$ref: "#/components/schemas/Change"
produce:
type: array
items:
$ref: "#/components/schemas/Change"
ChangeAmount:
type: object
required:
- account
- resource
- amount
properties:
account:
type: string
enum: [USER, DEST, HALLIDAY, PROCESSING_ADDRESS, REV_SHARE, BRIDGE]
resource:
$ref: "#/components/schemas/Resource"
amount:
$ref: "#/components/schemas/Amount"
EffectAmount:
type: object
required:
- consume
- produce
properties:
consume:
type: array
items:
$ref: "#/components/schemas/ChangeAmount"
produce:
type: array
items:
$ref: "#/components/schemas/ChangeAmount"
Change:
type: object
required:
- account
- resource
- amount
properties:
account:
type: string
enum: [USER, DEST, HALLIDAY, PROCESSING_ADDRESS, REV_SHARE, BRIDGE]
resource:
$ref: "#/components/schemas/Resource"
amount:
type: string
Resource:
type: object
required:
- asset
- property
properties:
asset:
type: string
property:
type: string
enum: [APPROVAL, BALANCE]
tags:
- name: Chains
description: Blockchain network information and configuration
- name: Assets
description: Asset information, discovery, and supported asset pairs
- name: Payments
description: Core payment operations including quotes, confirmation, and status tracking
```
# One-Time Wallet (OTW) or Deposit Address
Source: https://docs.halliday.xyz/pages/otw
Each unique payment begins with a new onchain address called a **one-time wallet** (OTW) also known as the **deposit address**. The OTW is always different from the payment destination address. The Payments SDK widget UI refers to the OTW as a "one-time wallet" in the delivery details section.
Each onramp or swap will create a new OTW that is controlled only by the **owner wallet address** specified in the API call parameters. In the event a payment gets stuck or is funded after expiration, the owner address has the ability to sign transactions to recover the assets from the OTW or retry the payment.
A payment can be initiated by simply funding the OTW from any address. For fiat onramps specifically, the onramp providers will be instructed to send tokens to the deposit address after the user authorizes their fiat payment.
Using the OTW to encapsulate each unique payment enables the workflow protocol to orchestrate payments in a compliant and non-custodial manner.
### Processing Addresses
Throughout the onchain execution of a payment, tokens may be transferred between addresses that are controlled by the owner wallet address. These addresses are called processing addresses.
These addresses are used once during a single payment for execution of swaps via DEX, bridging between chains, and more. Each processing address used, including its chain, is detailed in every payment API JSON response body.
Here is an example returned from the payment status or history endpoint under the key `processing_address`:
```json theme={null}
[
{
"chain": "base",
"address": "0x9B6454662fA67674b9D15a8d4C49204CbFF81BA1"
},
{
"chain": "story",
"address": "0xb09B6d506450Cadd32b0EC588Dd7D980A5e0FC7B"
}
]
```
The deposit address described in the previous section is included in the collection of processing addresses for a payment. The destination address, which is typically the user's wallet, is not included in the processing addresses.
# Payment Flows
Source: https://docs.halliday.xyz/pages/payment-flows
The Halliday API orchestrates complex payment flows across multiple providers and chains. This page illustrates how funds move through the system for different payment scenarios.
For rapid integrations, a customizable and feature-complete web user interface is available by [implementing the widget](/pages/payments-hello-world) using the Halliday Payments SDK.
For integrations that require deeper granularity see the [Halliday API](/pages/api-quickstart).
## Onramp to any token on any chain
Implementing onramping from fiat to a crypto asset is a challenging endeavor for the developer. Halliday makes this easy by integrating global on and offramp providers and utilizing a noncustodial cross-chain workflow protocol. Workflows can include onramping, offramping, swapping, and even custom actions.
The Payments SDK widget UI features a ramp optimized for conversion with a simple drop-in integration. The following is the order of operations in an onramp workflow using the Halliday API.
* Confirm that there is a valid route for the input and output tokens using the API.
* Request up-to-date quotes by passing the set of providers to use (Moonpay, Stripe, Coinbase, et al) as well as the input fiat currency and amount to the API.
* Select a quote returned from the API and accept it by passing the payment ID, state token, owner address and destination address to the API.
* The onramp provider checkout URL will be in the response. The user will choose their method of payment, provide payment info, and in their first purchase with the provider, input KYC information. After checkout, the onramp provider will fund the payment with tokens onchain automatically.
* Poll the status endpoint using the payment ID to monitor the progress of the onramp until it completes onchain.
**See the developer [API quickstart guide](/pages/api-quickstart) to get started building a Halliday payments integration for fiat onramps with payment providers and centralized exchanges.**
## Onchain and cross-chain token swaps
Single and cross-chain swaps are core features of Halliday Payments, enabled by the workflow protocol.
The following is the order of operations of a token swap using the API.
* Confirm that there is a valid route for the input and output tokens using the API.
* Request up-to-date quotes by passing the input and output tokens as well as the input token amount to the API.
* Select a quote returned from the API and accept it by passing the payment ID, state token, owner address and destination address to the API.
* Using the deposit details in the next instruction, transfer input tokens from the funder to the payment deposit address.
* Poll the status endpoint using the payment ID to monitor the progress of the swap until it completes onchain.
**See the developer [API quickstart guide](/pages/api-quickstart) to get started building a Halliday payments integration for swaps.**
## Token Recovery and Withdrawal Flows
In some instances, an onramp or swap can fail, in which case the developer can provide options for their user to proceed. An onramp or swap can fail if the output asset has a sudden price change, there is no longer sufficient liquidity in a DEX pool, or an unforeseen onchain state change occurs.
Developers can remedy the situation with a recovery or a withdrawal. A **recovery** resubmits the onramp or swap with a new quote. A **withdrawal** withdraws the input asset from the OTW back to the user's wallet address. **For an in-depth guide on payment retry and withdrawal flows, see the [API Recoveries](/pages/api-error-recovery-withdrawal) page.**
The Halliday Payments widget automatically monitors and provides recovery options for the user in real-time. Developers initializing their workflows with the Halliday API will need to handle these flows using relevant endpoints.
# Payments Hello World
Source: https://docs.halliday.xyz/pages/payments-hello-world
The fastest way to implement Halliday Payments is by utilizing the widget user interface.
## Try the Halliday Payments SDK Widget
Following this guide, or the [Widget Documentation](/pages/payments-sdk-docs) guide will result in an onramp expeirence like the following.
Try out onramping to an external wallet with this button:
### Integrate Halliday Payments
Follow these steps to integrate Halliday Payments in just a few minutes.
To get set up with API access for Halliday Payments, please reach out to [partnerships@halliday.xyz](mailto:partnerships@halliday.xyz).
First, install the Halliday Payments SDK into a web project.
```shell copy theme={null}
npm install @halliday-sdk/payments
```
```shell copy theme={null}
yarn add @halliday-sdk/payments
```
```html copy theme={null}
```
Next, integrate Halliday Payments into an existing application with a few lines of code. This example is in a React.js project.
```tsx copy theme={null}
import { openHallidayPayments } from "@halliday-sdk/payments";
// openHallidayPayments will open a popup that begins the payments flow.
// Customize the button to match a DApp's existing styles.
```
```html copy theme={null}
```
Run the app page and try onramping or swapping tokens with the newly implemented Halliday Payments Payments Widget. Try it now using the **button** above.
# Halliday Payments SDK Widget Documentation
Source: https://docs.halliday.xyz/pages/payments-sdk-docs
The Halliday Payments Widget allows users to perform onramps, swaps, and exchange withdrawals to or from any chain or token with minimal integration effort.
It provides the most rapid integration of Halliday Payments with a feature-rich configuration.
To have more fine-grained control over the user interface, review the [Halliday API](/pages/halliday-api-docs) documentation.
***
## Try the Halliday Payments SDK Widget
Following this guide, or the [Payments Hello World](/pages/payments-hello-world) guide will result in an onramp expeirence like the following.
Try out onramping to an external wallet with this button:
## Installation
Install the Payments SDK, which is available on NPM.
```shell theme={null}
npm install @halliday-sdk/payments
```
```shell theme={null}
yarn add @halliday-sdk/payments
```
Next the SDK can be imported into a front-end TypeScript or JavaScript project.
```typescript copy theme={null}
import { openHallidayPayments } from '@halliday-sdk/payments'
```
## Options
The `openHallidayPayments` function opens the Halliday Payments widget and accepts the following configuration options:
| Name | Type | Description |
| :----------------------------------- | :----------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------- |
| `apiKey` | string | Your API key for authorization. |
| `destinationAddress` (optional) | string | The address of the destination of the widget. |
| `inputs` (optional) | Asset\[] | The input assets for the widget. |
| `outputs` (optional) | Asset\[] | The output assets for the widget. |
| `onramps` (optional) | RampName\[] | The onramps for the widget. |
| `offramps` (optional) | RampName\[] | The offramps for the widget. |
| `customStyles` (optional) | object ([CustomStyles](/pages/payments-sdk-docs#type-definitions)) | A list of custom styles to show in the widget. See [customizing styles](#customizing-styles). |
| `targetElementId` | string | The ID of the DOM element where the widget should be embedded. Required if `windowType` is "EMBED". |
| `windowType` (optional) | "MODAL" \| POPUP" \| "EMBED" | The desired display mode of the widget. Default is "MODAL". |
| `statusCallback` (optional) | StatusCallback | Callback to receive status events. |
| `owner` (optional) | Owner | Owner wallet configuration with address and signing functions. |
| `funder` (optional) | Funder | Funder wallet configuration with signing functions. |
| `redirects` (optional) | object ([Redirects](/pages/payments-sdk-docs#type-definitions)) | Post-transaction redirect configuration. |
| `paymentCategoryOrder` (optional) | PaymentCategoryOrder | Display order of payment categories. The "open-\*" item's dropdown will be opened on widget load. Only the first item in the list can be open. |
| `fontName` (optional) | FontName | Font for the widget. Default is 'Haffer'. 'Wudoo-mono' or 'Inter' available. |
| `headerTitle` (optional) | string | Custom title for the widget header. |
| `showSkeleton` (optional) | boolean | (EMBED only) Show animated loading skeleton while widget loads. Default: `true`. |
| `skeletonBackgroundColor` (optional) | string | (EMBED only) Custom skeleton background color or CSS gradient. |
### Type Definitions
```ts theme={null}
type Address = string;
type TypedData = string;
interface TransactionRequest {
to: string;
from?: string;
nonce?: number;
gasLimit?: string | number | bigint;
gasPrice?: string | number | bigint;
maxPriorityFeePerGas?: string | number | bigint;
maxFeePerGas?: string | number | bigint;
data?: string;
value?: string | number | bigint;
chainId: number;
}
interface TransactionReceipt {
transactionHash?: string;
blockHash?: string;
blockNumber?: number;
from?: string;
to?: string;
// Preserves the original receipt from ethers or viem
rawReceipt: any;
}
type WindowType = "MODAL" | "EMBED" | "POPUP";
interface CustomStyles {
primaryColor?: string;
backgroundColor?: string;
/**
* In popup mode, the border only shows when the window is resized and made larger.
* If you would like there to be no border at all, you can set it to the same color as backgroundColor.
*/
borderColor?: string;
textColor?: string;
textSecondaryColor?: string;
accentColor?: string;
componentShadow?: string;
borderStyle?: "SQUARE" | "DEFAULT";
textMode?: "DEFAULT" | "ALL_UPPERCASE";
paymentCategoryGrouping?: "ATTACHED" | "BUTTON";
fontWeight?: "AUTO" | "MEDIUM";
baseFontSize?: `${number}px`;
backgroundStyle?: "OFF" | "BLUR";
baseBorderRadius?: `${number}px` | `${number}rem`;
}
type FontName = "haffer" | "wudoo-mono"| "inter";
type HeaderTitle = string;
type PaymentCategoryOrder = Array<
"wallet" | "open-wallet" | "exchange" | "open-exchange" | "cash" | "open-cash" | "deposit"
>;
interface Redirects {
redirectUrl: string; // URL
ctaText: string;
secondaryRedirectUrl?: string; // URL, must be provided with secondaryCtaText
secondaryCtaText?: string; // Must be provided with secondaryRedirectUrl
}
type Asset = string; // Format: "chainId:tokenAddress" or native token identifier or fiat symbol
// i.e. 'moonpay'
type RampName = string; // Onramp/offramp provider names
type OrderStatus = any;
type StatusCallback = (status: OrderStatus) => void;
interface OwnerRole {
address: Address;
}
interface PaymentsWidgetSDKParams {
apiKey: string;
destinationAddress?: Address;
inputs?: Asset[];
outputs?: Asset[];
onramps?: RampName[];
offramps?: RampName[];
customStyles?: CustomStyles;
// windowType = "EMBED", targetElementId is required
targetElementId?: string;
windowType?: WindowType;
owner?: OwnerRole;
funder?: FunderRole;
statusCallback?: StatusCallback;
redirects?: Redirects;
paymentCategoryOrder?: PaymentCategoryOrder;
fontName?: FontName;
headerTitle?: HeaderTitle;
// Skeleton options (EMBED only)
showSkeleton?: boolean;
skeletonBackgroundColor?: string;
}
```
## Usage Patterns
### Using the wallet connector
Halliday can prompt users to choose a wallet to connect to the DApp. The wallet connector provides many options including MetaMask, Coinbase Wallet, Phantom, or Wallet Connect.
Clicking the button will trigger the connect-wallet prompt. If the application already prompts the user to connect a wallet, see [using a connected wallet](#using-a-connected-wallet).
```tsx copy theme={null}
import { openHallidayPayments } from "@halliday-sdk/payments";
openHallidayPayments({
apiKey: HALLIDAY_PUBLIC_API_KEY,
// USDC on Base
outputs: ["base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"],
});
```
***
### Using a connected wallet
The Halliday SDK can accept an existing wallet connection object that an app has already established.
With this flow, users do not need to reconnect their wallet using the Halliday external wallet modal, creating an optimal user experience for Web3 applications.
If the application does not already have a connected wallet, see [using the wallet connector](#using-the-wallet-connector).
Support for existing Viem, Wagmi, and Ethers.js wallet connections are available to Halliday SDK developers. These functions also allow the widget to utilize the proper account if the user switches the address or network in their wallet extension.
```ts copy theme={null}
// For Viem or Wagmi
import { connectWalletClient } from "@halliday-sdk/payments/viem";
// For Ethers.js
import { connectSigner } from "@halliday-sdk/payments/ethers";
```
#### Viem
```typescript copy theme={null}
import { openHallidayPayments } from "@halliday-sdk/payments";
import { connectWalletClient } from "@halliday-sdk/payments/viem";
import { createWalletClient, custom } from "viem";
import { base } from "viem/chains";
const connect = async () => {
const address = (await window.ethereum.request({ method: "eth_requestAccounts" }))[0];
const connectedWalletClient = connectWalletClient(() => {
return createWalletClient({
chain: base,
transport: custom(window.ethereum)
});
});
openHallidayPayments({
apiKey: HALLIDAY_PUBLIC_API_KEY,
// USDC on Base
outputs: ["base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"],
owner: { address, ...connectedWalletClient },
funder: { address, ...connectedWalletClient }
}).catch((error) => {
console.log("error.issues", error.issues);
});
};
```
#### Wagmi
```jsx copy theme={null}
import { useEffect, useRef } from "react";
import { openHallidayPayments } from "@halliday-sdk/payments";
import { connectWalletClient } from "@halliday-sdk/payments/viem";
import { useAccount, useWalletClient } from "wagmi";
export default function App() {
const { address, isConnected } = useAccount();
const { data: walletClient, isSuccess } = useWalletClient();
const ref = useRef(null);
useEffect(() => {
if (isConnected && isSuccess && walletClient && address) {
const connectedWalletClient = connectWalletClient(() => walletClient);
openHallidayPayments({
apiKey: HALLIDAY_API_KEY,
outputs: ["base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"],
windowType: "EMBED",
targetElementId: "halliday",
owner: { address, ...connectedWalletClient },
funder: { address, ...connectedWalletClient }
});
} else if (ref.current) {
ref.current.innerHTML = "";
}
}, [isConnected, isSuccess, walletClient, address]);
return (
);
}
```
#### Ethers.js
```typescript copy theme={null}
import { openHallidayPayments } from "@halliday-sdk/payments";
import { connectSigner } from "@halliday-sdk/payments/ethers";
import { BrowserProvider } from "ethers";
const connect = async () => {
const address = (await window.ethereum.request({ method: "eth_requestAccounts" }))[0];
const connectedSigner = connectSigner(() => {
return new BrowserProvider(window.ethereum).getSigner();
});
openHallidayPayments({
apiKey: HALLIDAY_PUBLIC_API_KEY,
// USDC on Base
outputs: ["base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"],
owner: { address, ...connectedSigner },
funder: { address, ...connectedSigner }
}).catch((error) => {
console.log("error.issues", error.issues);
});
};
```
#### Dynamic
The following code example is a demonstration of passing a Dynamic embedded wallet provider to the Halliday SDK in a full React app which is available in the [SDK example apps section](/pages/payments-sdk-example-apps).
```jsx theme={null}
import { useDynamicContext, useIsLoggedIn } from '@dynamic-labs/sdk-react-core'
import { useAccount, useWalletClient } from 'wagmi'
import { openHallidayPayments } from '@halliday-sdk/payments'
import { connectWalletClient } from '@halliday-sdk/payments/viem'
const HALLIDAY_PUBLIC_API_KEY = import.meta.env.VITE_HALLIDAY_API_KEY
function App() {
const { sdkHasLoaded, setShowAuthFlow, handleLogOut } = useDynamicContext();
const isLoggedIn = useIsLoggedIn();
const { address, isConnected } = useAccount();
const { data: walletClient, isSuccess } = useWalletClient();
const launchHalliday = async () => {
if (!isConnected || !address) return;
let connectedWalletClient;
if (walletClient && isSuccess) {
// Remember to add Base in the Dynamic dashboard beforehand (see README)
connectedWalletClient = connectWalletClient(() => walletClient);
} else {
// Using window.ethereum EIP-1193 wallet like MetaMask via Dynamic
connectedWalletClient = window.ethereum;
}
openHallidayPayments({
apiKey: HALLIDAY_PUBLIC_API_KEY,
outputs: ['base:0x', 'base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913'],
windowType: 'MODAL',
owner: { address, ...connectedWalletClient },
funder: { address, ...connectedWalletClient }
}).catch(e => console.error('Halliday error:', e.issues));
};
if (!sdkHasLoaded) {
return
Loading...
}
if (!isLoggedIn) {
return (
)
}
return (
Wallet: {address}
);
}
export default App
```
#### Privy
In addition to the following code example, a demonstration of passing a connected Privy wallet to the Halliday SDK in a full React app is available in the [SDK example apps section](/pages/payments-sdk-example-apps).
```jsx theme={null}
import { openHallidayPayments } from '@halliday-sdk/payments'
import { usePrivy, useWallets } from '@privy-io/react-auth'
import { connectSigner } from '@halliday-sdk/payments/ethers'
import { BrowserProvider } from 'ethers'
function Component() {
const { ready, authenticated, login, logout } = usePrivy()
const { wallets } = useWallets()
const connect = async () => {
// Get the embedded wallet (or find the one you want)
// const wallet = wallets.find(w => w.walletClientType === 'privy');
const wallet = wallets[0];
const provider = await wallet.getEthereumProvider()
const connectedSigner = connectSigner(() => {
return new BrowserProvider(provider).getSigner()
});
openHallidayPayments({
apiKey: HALLIDAY_PUBLIC_API_KEY,
outputs: ["base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"],
owner: { address: wallet.address, ...connectedSigner },
funder: { address: wallet.address, ...connectedSigner }
}).catch((error) => {
console.log('error.issues', error.issues)
});
};
// ...
return (
...
);
}
export default Component
```
***
### Preloading the Widget
For the best user experience, the widget can be preloaded so it appears instantly when shown.
The `initializeClient` call should be done in the initial loading phase of the app.
Later, the `openHallidayPayments` call can be executed once the widget user interface is ready to be shown to the user.
```typescript copy theme={null}
import { initializeClient } from "@halliday-sdk/payments";
// Call early in your app (e.g., on mount)
await initializeClient({
apiKey: HALLIDAY_PUBLIC_API_KEY,
onReady: () => { console.log('Preloaded and ready') }, // optional
onError: (error) => { console.error(error) }, // optional
});
// Next, call openHallidayPayments
```
***
### Embedding as an iframe
By default, the Payments Widget opens as a modal overlaying the web page. It can also be opened as a popup window.
Another option is embed it within a page using an `