# 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.). If a payment output is >= $300 USD, the owner address will need to produce a EVM_OWNER signature. The response will include a USER_VERIFY next_instruction instead of funding details. In this case, prompt the user to sign the verification payloads via signTypedData, then submit a ContinueConfirmPaymentRequest to this same endpoint to complete confirmation. Once an owner address has produced an EVM_OWNER signature, subsequent payments with that owner address will not require the signature again. # 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. # Delete a webhook Source: https://docs.halliday.xyz/api-reference/webhooks/delete-a-webhook /public/openapi.yaml delete /orgs/webhooks Delete a registered webhook, identified by its `label`. # List webhooks Source: https://docs.halliday.xyz/api-reference/webhooks/list-webhooks /public/openapi.yaml get /orgs/webhooks List the webhooks registered for your org. The signing secret is never included in this response. # Register a webhook Source: https://docs.halliday.xyz/api-reference/webhooks/register-a-webhook /public/openapi.yaml post /orgs/webhooks Register an HTTPS endpoint to receive signed event notifications when a workflow reaches a terminal state. Halliday delivers each event as an HTTP `POST` to your registered `url`, so the endpoint must accept `POST` requests. On success the response includes a `signing_secret` that is **only ever returned on creation and rotation** — store it immediately, because it cannot be retrieved again from the list endpoint. Authenticate with a secret API key that has webhook access. Publishable keys cannot manage webhooks. # Rotate the signing secret Source: https://docs.halliday.xyz/api-reference/webhooks/rotate-the-signing-secret /public/openapi.yaml post /orgs/webhooks/rotate-secret Generate a new signing secret for a webhook. The new key is added immediately and signs alongside any existing keys, so deliveries carry multiple `v1=` signatures during the overlap and you can roll the secret in your verifier without downtime. The previous keys are only phased out if you pass `retire_after` (floored to at least 24h from now). **Omit `retire_after` and the previous secret stays active indefinitely** — both keep signing. The response returns the webhook `id` and the new `signing_secret`; store it now, as it is not retrievable later. # Update a webhook Source: https://docs.halliday.xyz/api-reference/webhooks/update-a-webhook /public/openapi.yaml patch /orgs/webhooks Update a registered webhook. `label` identifies which webhook to update and cannot be changed. The editable fields are **`url`** and **`auth_header`** (pass `auth_header: null` to remove a previously set header). `event_types` is not updatable — to change which events a webhook subscribes to, delete it and create a new one. To change the signing secret, use the rotate-secret endpoint. A successful update returns `200` with an empty body. # Workflow status changed Source: https://docs.halliday.xyz/api-reference/webhooks/workflow-status-changed /public/openapi.yaml webhook workflowStatusChanged This is the HTTP `POST` request **Halliday sends to your registered `url`** when a workflow reaches a terminal state — it is not an endpoint you call. Respond with any `2xx` within the 10-second timeout to acknowledge; anything else (or a timeout) is treated as a failed delivery and retried. ## Verifying the signature Each delivery carries an `X-Halliday-Signature` header: a comma-separated list of `v1=0x` values, where each `` is `HMAC-SHA256(raw_body)` computed with an active signing secret (used as a UTF-8 string), prefixed with `0x`. During a secret rotation more than one signature is present — treat the request as valid if **any** entry matches. Always verify against the **raw request body**, before JSON parsing. If you configured an `auth_header` when registering the webhook, Halliday also includes that custom header on every delivery — check it alongside the signature. ```js const crypto = require("crypto"); function verifyHallidaySignature(rawBody, headerValue, signingSecret) { const expected = crypto .createHmac("sha256", signingSecret) // secret used as UTF-8 string .update(rawBody) .digest("hex"); return headerValue .split(",") .map((s) => s.trim().replace(/^v1=/, "").replace(/^0x/, "")) .some((sig) => sig.length === expected.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected)) ); } ``` ## Delivery & retry semantics - **Acknowledgement:** any `2xx` response within the 10-second timeout. - **Retries:** up to 12 attempts within a 24-hour window, after which the delivery is marked `FAILED`. - **Backoff:** exponential with jitter — roughly 1m, 5m, 15m, 1h, 2h, 4h, then 8h between attempts. - **Idempotency:** a given `(workflow_id, webhook, event_type)` is enqueued once. De-duplicate on the delivery `id`, since a retried delivery reuses the same `id`. # 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 unforeseen setbacks. Halliday was built to be robust under all of these conditions within the ever-growing ecosystem of onchain protocols. The Halliday [OTWs](/pages/otw) are **self-custodial**. Users are always the sole controllers of these addresses through their wallet. ## Recoveries Halliday 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`), the balance, and the `withdraw_account` type. Check that the balance is greater than zero. If so, a recovery can be performed. Entries with `value.kind` of `error` indicate a balance lookup failure and should be skipped. When displaying the estimated output to the user, use `amount - withdrawal_fee` (the net amount). When calling the withdraw endpoint, use the full `amount`. * [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. Also, `state_token`, `owner_address`, and `destination_address` are required parameters for 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_amounts`: The returned amount of token from the prior `POST /payments/balances` endpoint call. * `recipient_address`: The address of the new payment's deposit address. * `withdraw_account`: The `withdraw_account` value from the balance result (`INTENT` or `SPW`). * The response includes a `signature_type` field (`EIP712` or `EIP191`) and a `withdraw_authorization` string. The owner wallet must sign this authorization: * If `signature_type` is `EIP712`: Parse `withdraw_authorization` as JSON and sign using `signTypedData`. * If `signature_type` is `EIP191`: Sign the `withdraw_authorization` string directly using `signMessage`. * Next call the [confirm withdraw endpoint](/api-reference/payments/confirm-withdrawal-request) (`POST /payments/withdraw/confirm`) with the newly created `signature` and the `state_token` returned by the withdraw response (pass it back unmodified). 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`), the balance, and the `withdraw_account` type. Check that the balance is greater than zero. If so, a recovery can be performed. Entries with `value.kind` of `error` indicate a balance lookup failure and should be skipped. When displaying the estimated output to the user, use `amount - withdrawal_fee` (the net amount). When calling the withdraw endpoint, use the full `amount`. * 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_amounts`: 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. * `withdraw_account`: The `withdraw_account` value from the balance result (`INTENT` or `SPW`). * The response includes a `signature_type` field (`EIP712` or `EIP191`) and a `withdraw_authorization` string. The owner wallet must sign this authorization: * If `signature_type` is `EIP712`: Parse `withdraw_authorization` as JSON and sign using `signTypedData`. * If `signature_type` is `EIP191`: Sign the `withdraw_authorization` string directly using `signMessage`. * Next call the [confirm withdraw endpoint](/api-reference/payments/confirm-withdrawal-request) (`POST /payments/withdraw/confirm`) with the newly created `signature` and the `state_token` returned by the withdraw response (pass it back unmodified). This signature will be executed onchain automatically and transfer the assets. ### Example Withdrawal Signature Request The `POST /payments/withdraw` endpoint returns a `signature_type` field indicating the required signature method. When `signature_type` is `EIP712`, the `withdraw_authorization` is a JSON string to parse and sign using `signTypedData`. When `signature_type` is `EIP191`, sign the `withdraw_authorization` string directly using `signMessage`. The following is an example EIP-712 withdrawal signature request on EVM chains, 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": [ { "kind": "other", "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 JS 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 API.
In the example, a user would be able to onramp directly to MEGA on MegaETH with a provider like Stripe. The app allows a user to connect their MegaETH wallet 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 }) }); ``` If the payment output is >= \$300 USD and the owner address has not been verified, the confirm response will include a `USER_VERIFY` next instruction. The user must sign the verification payloads before proceeding. A `while` loop handles up to two verification round-trips (owner verification followed by withdrawal simulation for large payments). ```js theme={null} // From onContinueButtonClick function let confirmResult = await acceptQuote(); const paymentId = confirmResult.payment_id; // Handle user verification if required (>= $300 owner verify, >= $1M withdrawal sim) // Loop to handle up to two verification round-trips while (confirmResult.next_instruction?.type === 'USER_VERIFY') { const { verification_token, verifications } = confirmResult.next_instruction; const provider = new ethers.BrowserProvider(window.ethereum); const signer = await provider.getSigner(); const signatures = await Promise.all( verifications.map(async (v) => { let signature; if (v.signature_type === 'EIP712') { const typedData = JSON.parse(v.payload); const { EIP712Domain, ...types } = typedData.types; signature = await signer.signTypedData(typedData.domain, types, typedData.message); } else { signature = await signer.signMessage(v.payload); } return { reason: v.reason, signature_type: v.signature_type, signature }; }) ); // Submit verification signatures to confirm endpoint const verifyRes = await fetch('https://v2.prod.halliday.xyz/payments/confirm', { method: 'POST', headers: { 'Authorization': 'Bearer ' + HALLIDAY_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ verification_token, signatures }) }); if (verifyRes.status === 409) { break; // Already confirmed — treat as success } else if (verifyRes.status === 400) { alert('Quote expired. Please try again.'); return; } else if (verifyRes.status === 401) { console.warn('Signature verification failed, retrying...'); continue; } confirmResult = await verifyRes.json(); } const onrampUrl = confirmResult.next_instruction.funding_page_url; ``` 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} 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 API.
In the example, a user would be able to swap from USDC on Base to MEGA on MegaETH using the Halliday 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) }); const confirmedPayment = await res.json(); ``` If the payment output is >= \$300 USD and the owner address has not been verified, the confirm response will include a `USER_VERIFY` next instruction. The user must sign the verification payloads before proceeding. A `while` loop handles up to two verification round-trips (owner verification followed by withdrawal simulation for large payments). ```js theme={null} // Handle user verification if required (>= $300 owner verify, >= $1M withdrawal sim) // Loop to handle up to two verification round-trips let swapData = confirmedPayment; while (swapData?.next_instruction?.type === 'USER_VERIFY') { const { verification_token, verifications } = swapData.next_instruction; const provider = new ethers.BrowserProvider(window.ethereum); const signer = await provider.getSigner(); const signatures = await Promise.all( verifications.map(async (v) => { let signature; if (v.signature_type === 'EIP712') { const typedData = JSON.parse(v.payload); const { EIP712Domain, ...types } = typedData.types; signature = await signer.signTypedData(typedData.domain, types, typedData.message); } else { signature = await signer.signMessage(v.payload); } return { reason: v.reason, signature_type: v.signature_type, signature }; }) ); // Submit verification signatures to confirm endpoint const verifyRes = await fetch('https://v2.prod.halliday.xyz/payments/confirm', { method: 'POST', headers: { 'Authorization': 'Bearer ' + HALLIDAY_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ verification_token, signatures }) }); if (verifyRes.status === 409) { break; // Already confirmed — treat as success } else if (verifyRes.status === 400) { alert('Quote expired. Please try again.'); return; } else if (verifyRes.status === 401) { console.warn('Signature verification failed, retrying...'); continue; } swapData = await verifyRes.json(); } ``` 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({ 'categories[]': '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 Ethers 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 Ethers 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 `COMPLETE` 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/HallidayApiDynamicExamplesWagmi)
## 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 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. Get a free API key at [dashboard.halliday.xyz](https://dashboard.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 supports hundreds of crypto assets for fiat onramps from centralized exchanges and payment providers, as well as onchain swaps via bridges and DEXs. Before initializing 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`. Pass at least one input asset to `/assets/available-outputs` to get meaningful results. 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 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 displayed in a UI so the user can select 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. If the payment output is >= \$300 USD and the owner address has not been verified, the response will have a status of `UNCONFIRMED` with a `USER_VERIFY` next instruction instead. The user must sign the verification payloads and submit them back to `POST /payments/confirm` as a `ContinueConfirmPaymentRequest` before the payment can proceed. See [Owner verification](/pages/api-status#owner-verification-user_verify) for details. 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 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 displayed in a UI so the user can select 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. If the payment output is >= \$300 USD and the owner address has not been verified, the response will have a status of `UNCONFIRMED` with a `USER_VERIFY` next instruction instead. The user must sign the verification payloads and submit them back to `POST /payments/confirm` as a `ContinueConfirmPaymentRequest` before the payment can proceed. See [Owner verification](/pages/api-status#owner-verification-user_verify) for details. 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 Poll `GET /payments?payment_id={payment_id}` 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` or `UNCONFIRMED`. If the status is `UNCONFIRMED`, the owner must complete verification before the payment can proceed (see [owner verification](#owner-verification-user_verify) below). 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` or `UNCONFIRMED`. This object details the next instruction required to execute the payment. **Type** The `next_instruction.type` value can be `ONRAMP` for fiat onramp payments, `TRANSFER_IN` for swaps, or `USER_VERIFY` when owner verification is required. **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. **Owner verification (USER\_VERIFY)** When a payment output is >= \$300 USD and the owner address has not yet been verified, the `next_instruction.type` will be `USER_VERIFY` and the payment status will be `UNCONFIRMED`. The instruction contains a `verification_token` and a `verifications` array. Each entry in the array has a `reason` (`EVM_OWNER` or `EVM_WITHDRAWAL`), a `signature_type` (`EIP712` or `EIP191`), and a `payload` string. Verification thresholds: | Order value | Verification | Behavior | | ----------- | ------------ | ---------------------------------------------------- | | \< \$300 | None | Proceeds directly to funding | | >= \$300 | `EVM_OWNER` | Skipped if the owner address has previously verified | For each verification entry, prompt the user to sign the `payload` using the method that matches its `signature_type`: `signTypedData` for `EIP712` and a personal message signature (`personal_sign` / `signMessage`) for `EIP191`. Then construct a `ContinueConfirmPaymentRequest` with the `verification_token` and a `signatures` array, and submit it to `POST /payments/confirm`. Each entry in `signatures` requires `reason`, `signature_type`, and `signature`, where `reason` and `signature_type` match the corresponding verification entry. Once an owner address has produced an `EVM_OWNER` signature, subsequent payments with that address will not require the signature again. `EVM_WITHDRAWAL` verification is always required when applicable and is never skipped. If the user abandons before completing verification, the payment remains `UNCONFIRMED`. Once the quote expires, a new quote must be requested. **Verification error handling** | Status code | Meaning | Action | | ----------- | -------------------------------------- | ---------------------------------------------------------------------- | | 200 | Verification passed | Continue — `next_instruction` is now `TRANSFER_IN` or `ONRAMP` | | 400 | Quote expired or invalid input | Re-quote from scratch | | 401 | Signature verification failed | Prompt the user to sign the payloads again and submit fresh signatures | | 403 | Payment belongs to a different account | Do not retry — check the API key and payment ID | | 404 | Payment not found or expired | Re-quote from scratch | ## 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. The `PENDING`, `WITHDRAW_PENDING`, and `UNCONFIRMED` statuses indicate that a payment is still in progress, so a front-end application should continue polling `GET /payments` for status updates while a payment is in any of these states. All other statuses indicate that the payment is halted and polling can stop. A halted status is not necessarily terminal, however — further action can still change it. For example, a payment that becomes `EXPIRED` and is then funded can be withdrawn, transitioning its status to `WITHDRAWN`. ### Pending The `PENDING` status is the initial 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. ### Unconfirmed The `UNCONFIRMED` status indicates that the payment requires owner verification before it can proceed. This occurs when the payment output is >= \$300 USD and the owner address has not yet produced an EVM\_OWNER signature. The response will include a `USER_VERIFY` next\_instruction with payloads for the user to sign. Once verification is submitted via a `ContinueConfirmPaymentRequest` to `POST /payments/confirm`, the status transitions to `PENDING`. ### 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 MEGA on MegaETH. The payment status changes to `COMPLETE` as soon as the transaction to transfer MEGA tokens to the user's wallet address on MegaETH 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. ### Failed The `FAILED` status indicates that a payment has failed to complete due to an onchain execution failure. Failed payments can be retried or assets in relevant OTW addresses can be withdrawn by the owner wallet using EIP712 signatures. ### 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. ### Withdraw Pending The `WITHDRAW_PENDING` status indicates that a withdrawal of the payment's funds is in progress while the payment has not reached completion. Once the withdrawal transfer out of the OTW deposit address is confirmed, the status transitions to `WITHDRAWN`. ### 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. In the scenario that a user accidentally funds a payment with an improper input token, and then withdraws it, the status will become `WITHDRAWN`. However, the user can subsequently fund the payment with the correct token, and the status will become `PENDING` once again. The payment can then execute as intended and eventually reach a `COMPLETE` status. ### 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", "resource": { "asset": "chain:0xtoken_contract_address", "property": "BALANCE" }, "amount": "0.0911234", "tx_id": "0xanother_tx_hash_here" } ], "produce": [ { "account": "DEST", "resource": { "asset": "chain:0xtoken_contract_address", "property": "BALANCE" }, "amount": "0.0911234", "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", "address_family": "EVM", "native_currency": { "name": "Ether", "symbol": "ETH", "decimals": 18 }, "is_testnet": false, "explorer": "https://etherscan.io/", "image": "...", "rpc": "..." }, "arbitrum": { "chain_id": { "#": 42161 }, "network": "arbitrum", "address_family": "EVM", "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 transactionHash = payment.fulfilled.route .filter(step => step.type === 'ONCHAIN_STEP') .flatMap(step => step.net_effect.produce) .find(p => p.account === 'DEST' && p.tx_id) ?.tx_id; 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 Halliday provides a non-custodial blockchain payment network, which is immutable and completely verifiable onchain. ### Non-Custodial by Design Halliday is fully non-custodial. It does not custody, possess, or control user funds at any point in the flow. Moreover, Halliday never serves as a counterparty to the user - meaning, the user retains full legal ownership and technical control over their funds. * **No private keys.** Halliday never takes possession of, generates, or stores user private keys or seed phrases. * **No custody, no commingling.** User assets are never deposited into, pooled in, or held by Halliday-controlled accounts or wallets. * **No unilateral control.** Halliday cannot initiate, pause, reverse, freeze, seize, or recover user transactions or funds. It cannot move assets without a user's own signed authorization. * **User-directed execution.** Transactions are initiated and authorized by the user from their own wallet or account. Halliday's system cryptographically orchestrates the user's stated intent. Because users connect their own wallet and authorize every transaction, they remain in complete control of their assets. ### Compliance Program #### Sanctions and Illicit Finance Screening Halliday partners with [TRM Labs](https://www.trmlabs.com/) to support sanctions and illicit finance screening. This includes screening against applicable sanctions lists (including the U.S. Treasury Department's OFAC Specially Designated Nationals list), illicit finance activity, wallet-level risk assessments, and the restriction of access from prohibited jurisdictions. #### Identity Verification (KYC / AML) Identity verification and anti-money-laundering checks tied to fiat onramp and centralized exchange activity are performed by third-party onramp and centralized exchange providers, subject to their own regulatory obligations. Halliday integrates only with providers that maintain their own compliance programs appropriate to the services they provide. #### Data Privacy Halliday handles personal data in accordance with applicable privacy laws and its published Privacy Policy, applying data-minimization principles and limiting the collection of personal information to what is necessary to provide its services. For details on what data is collected and how it is used, see the [Privacy Policy](https://halliday.xyz/legal/privacy-policy). ### Security Halliday maintains security practices across its smart contracts, application infrastructure, internal operations, and third-party integrations. #### Smart Contract Audits Halliday's smart contract infrastructure has been independently reviewed by leading security firms, including [ChainSecurity](https://www.chainsecurity.com/), [Zellic](https://www.zellic.io/), and [Halborn](https://www.halborn.com/). Audit findings are reviewed, prioritized, and remediated before production deployment. #### Access Controls and Operational Security Halliday maintains internal controls designed to restrict access to production systems, administrative permissions, and sensitive operational workflows. Access is limited based on role, business need, and security requirements. #### Monitoring and Incident Response Halliday monitors its systems and integrations for reliability, security, and abnormal activity. Halliday’s internal Engineering team will investigate, escalate, and respond to potential security events. #### Third-Party Provider Security Halliday integrates with third-party providers for services such as onramping, exchange connectivity, bridging, blockchain analytics, and infrastructure. These providers remain responsible for the security and compliance of the services they operate. Halliday evaluates providers based on their role in the transaction flow and the sensitivity of the services they provide. #### Responsible Disclosure Security issues may be reported to Halliday through its designated security contact [security@halliday.xyz](mailto:security@halliday.xyz). Halliday reviews reported vulnerabilities and takes appropriate steps to investigate and remediate confirmed issues. ### Learn More For more information, see the [Privacy Policy](https://halliday.xyz/legal/privacy-policy) and [Terms of Use](https://halliday.xyz/legal/terms-of-use). # Frequently Asked Questions Source: https://docs.halliday.xyz/pages/faq ## Halliday General FAQ ### What is Halliday? Halliday 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 is built on the Workflow 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 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 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 be used for? Halliday 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) ### Is there slippage or other fees for onramping to USDC? Fees for USDC onramps differ based on the onramp provider that the user selects at the time of an onramp (Stripe, Coinbase, et al), and the blockchain in which the USDC onramp initially occurs. Coinbase has no onramp fees for USDC. Stripe has a percentage fee that differs based on the fiat source. Users will see different onramp providers available to them based on their assumed jurisdiction when their onramp quote is created. Blockchains like Base have zero transfer fees for USDC whereas Ethereum mainnet will have relatively high gas costs for transfers. If the chain of the onramp destination address does not have a native USDC deployment, the USDC will be bridged from a chain that does, which will add to the total fees the user pays. For blockchains with native USDC, Halliday implements Circle's CCTP for bridging which has lower fees that are posted on Circle's website. ### Does bridging rely on liquidity pools or bypass it via native USDC bridging? Halliday supports several token bridges including Circle CCTP. Depending on the destination token of a payment, a supported bridge will be included in a quoted path. Quotes presented to the user at the time of a payment are ordered by the highest output quote first. ### What caveats are there for non-USD onramps? Depending on a user's bank, there may be an FX charge if the fiat onramp charge is in a different currency from the user's account. Users have experienced that banks may create friction with card payments for onramps, usually with declined charges. These policies are set and controlled by the user's bank. There may be different results with different onramp providers. Debit cards tend to have higher success rates than credit cards. ### Which Centralized Exchanges (CEXs) are currently supported for direct withdrawals to a blockchain? Any CEX that supports sending major tokens, such as USDC on Ethereum, to an EOA can be used to directly send assets via Halliday. The initial blockchain in which the USDC withdrawal occurs may be different from the destination blockchain. Halliday's automated workflows will execute the interim bridging and swapping onchain from input token to output token. With a single interaction, the user will receive the output token in their wallet shortly after completing the transaction. Fees for bridging, DEX swaps, and onchain gas consumption are included in the quote the user confirms. These fees vary by onchain route and the present onchain conditions. Fees that users pay can optionally be covered by clients. Reach out to the Halliday team to enable this. ### Does the widget support bundled "Onramp/Withdrawal + Swap" actions? Example: a user withdraws USDC from a CEX or pays with a credit/debit card, and automatically receives tokens on a blockchain. Yes, Halliday workflows were made for this purpose. A payment is funded by the onramp provider or CEX after the user completes a checkout. Once the workflow is funded (usually by USDC) the necessary bridging and DEX operations are executed automatically and the user gets the output token in their wallet. Halliday can have a destination address set per payment, which is usually the user's wallet address or app account address. Liquidity can be sourced from bridges, DEXes, or by minting if applicable. ### What is the fee structure for using your onramp / CEX withdrawal services? Halliday does not charge a fee via the onramp or CEX. Halliday only charges a fee for completion of onchain delivery of funds. These amounts can be paid by the client, should they want to enable 1:1 deposits for their end users. ## Adding Token Support ### What does Halliday need in order to support deposits to a new token on a supported chain? The team at Halliday can add support for new tokens on blockchains that are already supported (see `/chains` API endpoint or [Supported Chains section](/pages/payment-method-support#supported-chains)). The following is the required information for adding support for new tokens: * Token contract address * Token symbol (e.g. BTC, ETH, SOL) * Chain name and chain ID in which the token is deployed * Specific liquidity sources such as DEX pool addresses, wrapper contract addresses, or mint contract addresses * Coingecko API ID and Coingecko icon image URL * Applicable bridges and their contract addresses if cross-chain routing is required ### What does Halliday need in order to support deposits to a new token on an unsupported chain? New chains can become supported by the Halliday API. The following information is required for supporting a new chain: * Chain name and chain ID * Names of the bridges that the chain supports * Addresses of supported bridge contracts * Block explorer website information (e.g. [https://etherscan.io/](https://etherscan.io/)) * RPC server address information * Hardfork information (if EVM) * Approximate block confirmation time in seconds * Wallet and transaction signature type information (if non-EVM) ## On/Offramp Help ### Onramps #### How can users onramp from fiat using Halliday? 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 JS SDK 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 JS SDK 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 JS SDK 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? The Halliday team is working on an offramp service designed to integrate effortlessly with existing apps, providing fast, secure, and reliable offramping to fiat currencies. ### Where can I find geographic availability for on & offramping? This information is available on the [Supported Payment Methods](/pages/payment-method-support) page. # 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 a free API key at dashboard.halliday.xyz](https://dashboard.halliday.xyz/) 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 a free API key at dashboard.halliday.xyz](https://dashboard.halliday.xyz/). ## Method Docs and Playground After getting an API key, check out the [Halliday 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 Documentation Source: https://docs.halliday.xyz/pages/home Welcome to the Halliday documentation. Developers can explore our guides and examples to integrate a seamless crypto deposit flow in minutes. Get your free API keys now in the Halliday Dashboard Create your first app with the Payments SDK in minutes Use the Claude Code or Codex Plugin to develop with Halliday Learn how to implement Halliday with the API directly ## About Halliday With Halliday, 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 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 email [partnerships@halliday.xyz](mailto:partnerships@halliday.xyz). Get a free API key at [dashboard.halliday.xyz](https://dashboard.halliday.xyz/). ## Use Cases Web3 teams use Halliday for the following use cases. ## Unified Deposit Experience For modern apps like prediction markets, Perp DEXes, and NeoBanks, depositing digital or fiat currencies can add a tremendous amount of scope and maintenance overhead. Halliday's unified deposit experience brings deposits with a native look and feel to any app with just a few lines of code. ### 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. Headless API-based integrations can be implemented as well. ### 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 the onchain orchestration occurs automatically. ### Automated Workflows Protocols can compose interactions across networks, like a purchase, a swap, a stake, and more with a single user 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 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.** [partnerships@halliday.xyz](mailto:partnerships@halliday.xyz) # AI Plugins and LLMs.txt Source: https://docs.halliday.xyz/pages/llms-info ## Halliday Claude Code Plugin For users developing with [Claude Code](https://claude.com/product/claude-code), Halliday now has an official plugin available to automate tedious tasks of development. The `/halliday` command with a guided menu of flows: * **Ask questions and learn** — Answers backed by a bundled reference library of the official documentation pages, SDKs, and live Halliday API lookups. * **Clone a sample application** — Pick from several example open source repos and walk through setup end-to-end. * **Check my integration** — Scan your codebase against an integration checklist and flag only the items that could cause issues with the Halliday integration. * **Look up a payment** — Fetch a payment by ID, show a clear status summary with explanation, and give status-specific next steps. The Halliday Claude Code plugin is now available in the [Anthropic community marketplace](https://github.com/anthropics/claude-plugins-community). After installing Claude Code on the command line, add the plugin using the Anthropic community marketplace: ``` ## official community marketplace claude plugin marketplace add anthropics/claude-plugins-community ## OR from our marketplace, version updated more frequently claude plugin marketplace add halliday claude plugin install halliday-payments ``` If already installed, update the plugin with this command: ``` ## From a shell claude plugin update halliday-payments@claude-community ## OR claude plugin update halliday-payments@halliday ## From within the Claude Code CLI /plugin update halliday-payments@claude-community ## OR /plugin update halliday-payments@halliday ``` To work on a personalized fork, the plugin can be utilized in development mode using git and these commands: ``` git clone https://github.com/HallidayInc/HallidayClaudePlugin.git claude --plugin-dir ./HallidayClaudePlugin ## Once in Claude Code, use this command to open the Halliday Claude Code plugin: /halliday ``` The Halliday Claude Code plugin is [open source on GitHub](https://github.com/HallidayInc/HallidayClaudePlugin/). ## Halliday Codex Plugin For users developing with [Codex](https://openai.com/codex/), Halliday also has an official plugin available to automate tedious tasks of development. The `halliday` skill with a guided menu of flows: * **Ask questions and learn** — Answers backed by a bundled reference library of the official documentation pages, SDKs, and live Halliday API lookups. * **Clone a sample application** — Pick from several example open source repos and walk through setup end-to-end. * **Check my integration** — Scan your codebase against an integration checklist and flag only the items that could cause issues with the Halliday integration. * **Look up a payment** — Fetch a payment by ID, show a clear status summary with explanation, and give status-specific next steps. The Halliday Codex plugin repository is itself a Codex plugin marketplace, so no clone is needed. After installing Codex on the command line, add the marketplace and install the plugin: ``` codex plugin marketplace add HallidayInc/HallidayCodexPlugin codex plugin add halliday-payments@halliday ``` Start a new `codex` session to pick up the skill, then invoke it with `$halliday` or just ask a Halliday question: ``` ## Once in Codex, use this command to open the Halliday Codex plugin: $halliday ``` To update to the latest version, run these commands and then start a new session: ``` codex plugin marketplace upgrade codex plugin add halliday-payments@halliday ``` The Halliday Codex plugin is [open source on GitHub](https://github.com/HallidayInc/HallidayCodexPlugin/). ## Export of the Documentation for LLMs 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.json` is here: [https://v2.prod.halliday.xyz/api](https://v2.prod.halliday.xyz/api). # 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_addresses`: ```json theme={null} [ { "chain": "base", "address": "0x9B6454662fA67674b9D15a8d4C49204CbFF81BA1" }, { "chain": "megaeth", "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 JS 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 non-custodial 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. * If the payment output is >= \$300 USD and the owner address has not been verified, the response will include a `USER_VERIFY` instruction. The user signs the verification payloads and submits them back to the confirm endpoint before proceeding. * 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 `GET /payments` 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 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, 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. * If the payment output is >= \$300 USD and the owner address has not been verified, the response will include a `USER_VERIFY` instruction. The user signs the verification payloads and submits them back to the confirm endpoint before proceeding. * Using the deposit details in the next instruction, transfer input tokens from the funder to the payment deposit address. * Poll the `GET /payments` 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 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. Withdrawals support multiple account types and signature methods (`EIP712` via `signTypedData` or `EIP191` via `signMessage`) depending on the chain. **For an in-depth guide on payment retry and withdrawal flows, see the [API Recoveries](/pages/api-error-recovery-withdrawal) page.** The Halliday JS SDK 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. # Payment Rail, Chain, & Token Support Source: https://docs.halliday.xyz/pages/payment-method-support ## Fiat on and offramps Halliday supports fiat-to-crypto onramps and offramps worldwide. Available payment rails depend on the payment providers Halliday integrates with, and each provider supports a different set of rails and regions. As a result, not every rail is available in every location. In non-sanctioned regions, Halliday's supported fiat onramps generally include: * Debit card * Credit card * Bank ACH deposit * Apple Pay * Google Pay * PayPal * Venmo * Coinbase For more detailed regional support, reach out to [partnerships@halliday.xyz](mailto:partnerships@halliday.xyz) or click the **Book a Sales Call** button above. ## Crypto Assets Halliday supports crypto assets as input and output for payments. Supported crypto assets can be found by querying the `/assets` [endpoint of the API](/api-reference/assets/get-asset-details). Get an API key for free in the [Halliday Dashboard](https://dashboard.halliday.xyz/). Specific payment [route support from input asset to output asset](https://docs.halliday.xyz/api-reference/assets/get-available-input-assets-for-a-given-output-asset) can also be checked using the API. Reach out to the team directly at [partnerships@halliday.xyz](mailto:partnerships@halliday.xyz) to request additional support for fiat currencies, tokens, or chains. ## Supported Chains The following blockchains are supported by Halliday: ## Supported Tokens All supported tokens are available as a destination token for fiat onramps and swaps. # SDK Widget Hello World Source: https://docs.halliday.xyz/pages/payments-hello-world The fastest way to implement Halliday is by utilizing the JS SDK widget user interface. ## Try the Halliday JS SDK Widget Following this guide, or the [JS SDK Widget Documentation](/pages/payments-sdk-docs) guide will result in an onramp experience like in the following demo. ### Integrate Halliday Follow these steps to integrate Halliday in just a few minutes. To get set up with API access for Halliday, get a free API key at [dashboard.halliday.xyz](https://dashboard.halliday.xyz/). First, install the Halliday JS 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 into an existing application with a few lines of code. This example is in a React.js project with [Wagmi](https://wagmi.sh/). ```tsx copy theme={null} import { useEffect } from "react"; import { createRoot } from "react-dom/client"; import { WagmiProvider, useAccount, useDisconnect, useWalletClient } from "wagmi"; import { mainnet, base } from "wagmi/chains"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { RainbowKitProvider, getDefaultConfig, useConnectModal, useAccountModal } from "@rainbow-me/rainbowkit"; import "@rainbow-me/rainbowkit/styles.css"; import { HallidayPaymentsProvider, useHallidayPayments } from "@halliday-sdk/payments/react"; import { connectWalletClient } from "@halliday-sdk/payments/viem"; const wagmiConfig = getDefaultConfig({ appName: "My App", projectId: PROJECT_ID, chains: [ mainnet, base ], }); const queryClient = new QueryClient(); function App() { const { openDeposit, updateWallets, isReady } = useHallidayPayments(); const { address } = useAccount(); const { data: walletClient } = useWalletClient({ assertChainId: false }); const { openConnectModal } = useConnectModal(); const { openAccountModal } = useAccountModal(); const { disconnect } = useDisconnect(); const owner = connectWalletClient(() => walletClient); const enabled = isReady && !!walletClient; useEffect(() => { updateWallets({ owner, deposit: { funders: [], destinationAddress: address }, }); }, [enabled, walletClient, address]); return (
); } createRoot(document.getElementById("root")).render( ); ```
```html copy theme={null} ```
Run the app page and try onramping or swapping tokens with the newly implemented Halliday Payments Widget. Try it now using the **button** above.
# Halliday JS SDK Widget Documentation Source: https://docs.halliday.xyz/pages/payments-sdk-docs The Halliday JS SDK 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 with a feature-rich configuration. To use the Halliday JS SDK, first get a free API key at [dashboard.halliday.xyz](https://dashboard.halliday.xyz/). ## Installation Install the SDK, which is available on NPM. ```shell theme={null} npm install @halliday-sdk/payments ``` ```shell theme={null} yarn add @halliday-sdk/payments ``` ## Initialization Next the SDK can be imported into a front-end TypeScript or JavaScript project. ```tsx copy theme={null} import { useHallidayPayments } from "@halliday-sdk/payments/react" ``` ```tsx copy theme={null} import HallidayPayments from "@halliday-sdk/payments" ``` This is an example configuration for initializing the SDK. More information on each parameter, and whether or not it is required, is detailed in the next section. Initializing the widget on page load or component mount will set up the widget in the background before the user clicks a deposit or withdraw button. Presenting the widget in a click event handler is shown in the [deposits section](/pages/sdk-deposits). ```tsx copy theme={null} createRoot(document.getElementById("root")).render( ); ``` ```tsx copy theme={null} import HallidayPayments from "@halliday-sdk/payments" import { connectWalletClient } from "@halliday-sdk/payments/viem"; import { createWalletClient, custom } from "viem"; import { base } from "viem/chains"; const [ address ] = await window.ethereum.request({ method: "eth_requestAccounts" }); const owner = connectWalletClient(() => createWalletClient({ chain: base, transport: custom(window.ethereum), }), ); const halliday = new HallidayPayments({ apiKey: HALLIDAY_PUBLIC_API_KEY, owner, deposit: { // USDC on Base outputs: [ "base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913" ], funders: [], destinationAddress: address, }, }); // More or updated config parameters can be passed later // using `updateConfig` e.g. add another wallet connection halliday.updateConfig({}); ``` ## Options The `HallidayPayments` constructor initializes the Halliday JS SDK instance and prepares the widget interface to subsequently be opened and displayed to the user. To open the SDK widget interface, like in a deposit button click event handler, use the `openDeposit` function. More on the deposit button pattern in the [SDK Deposits](/pages/sdk-deposits) section. More on [config and wallet updates](#config-and-wallet-updates) below. Both the constructor and `updateConfig` functions accept all of the following configuration options. | Name | Type | Description | | :--------------------------- | :--------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `apiKey` | string | The public API key for authorization. The only required parameter. | | `owner` (optional) | Owner | The end user / recipient identity. One of three variants: `direct`, `wallet-auth`, or `otp-auth`. See [Owner](#owner). | | `onConnectWallet` (optional) | () => void | Callback invoked when the widget asks the host to connect a wallet. Can be replaced live via `updateConfig`. | | `deposit` (optional) | object ([DepositConfig](#deposit)) | Deposit/onramp configuration group. | | `withdrawal` (optional) | object ([WithdrawalConfig](#withdrawal)) | Withdrawal/offramp configuration group. `withdrawal.funder` must be set before calling `openWithdrawal()`. | | `customStyles` (optional) | object ([CustomStyles](#customstyles)) | Custom styles for the widget. | | `fontName` (optional) | string | Font family name used inside the widget. Options are "haffer", "inter", "eb-garamond", "roboto-mono" | | `headerTitle` (optional) | string | Custom title for the widget header. | | `targetElementId` (optional) | string | The ID of the DOM element where the widget should be embedded. When set, the widget renders in EMBED mode; when absent, it renders as a full-screen MODAL. In React, use the [``](#embedding-the-sdk-widget) component instead. | ## Owner The `owner` is a self-custody wallet that controls the user's payment onchain. In the event a payment [becomes stuck](/pages/api-error-recovery-withdrawal), the user can generate an EVM signature to withdraw the assets or route them to a recovery payment. In addition to EOA users, owner configuration options are available below for users that either do not have a wallet at all or have a non-EVM compatible wallet. ### Owner - EOA This type of direct owner configuration is best for users that connect their own EOA wallet to the page like MetaMask, Rabby, Phantom, et al. ```js theme={null} const owner = { getAddress: async () => "0x...", // required, async signMessage: async ({ message }) => "...", // required signTypedData: async ({ typedData }) => "...", // required sendTransaction: async (tx, chain) => TransactionReceipt, // Optional walletName: "Bob's Account", // Optional, shown in the wallet selector }; ``` This object can be constructed from scratch or created using the Halliday SDK's [wallet connector functions](#using-the-wallet-connector). For examples with popular SDKs and embedded wallet providers, see the [using a connected wallet](#using-a-connected-wallet) section below. | Name | Type | Description | | :--------------------------- | :--------------------- | :----------------------------------------- | | `getAddress` | () => Promise\ | Returns the owner's wallet address. | | `signMessage` | SignMessage | Signs a plain message. | | `signTypedData` | SignTypedData | Signs EIP-712 typed data. | | `sendTransaction` (optional) | SendTransaction | Sends a transaction from the owner wallet. | | `walletName` (optional) | string | Display name shown in the widget. | ### Owner - Wallet Auth The user authenticates by providing an EVM signature confirming ownership of payments with their EVM wallet address. This is commonly used for owning payments that are initialized on non-EVM chains. If the user does not have an EVM wallet, an embedded wallet can be used with an emailed one-time passcode, which is detailed in the next section. | Name | Type | Description | | :---------------- | :---------------------------- | :------------------------------------------ | | `type` | `"wallet-auth"` | Variant discriminator. | | `walletType` | `"EVM"` \| `"SOL"` \| `"SUI"` | Chain family of the owner wallet. | | `address` | string | The owner's wallet address. | | `signAuthMessage` | SignAuthMessage | Signs the authentication challenge message. | ### Owner - OTP Auth Email one-time passcode authentication. The auto-generated embedded EVM wallet is the owner of payments. This method is a proper option if the user does not have a wallet or connects a non-EVM compatible wallet. | Name | Type | Description | | :-------- | :----------- | :------------------------ | | `type` | `"otp-auth"` | Variant discriminator. | | `address` | string | The user's email address. | ## Deposit The following are configuration options for onramps or cross-chain deposits of assets to a specified destination address e.g. the user's wallet or an in-app smart contract wallet. | Name | Type | Description | | :------------------- | :--------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `inputs` (optional) | Asset\[] | Source/funding assets. Asset strings use the format `"chain:tokenAddress"`. See [payment methods](/pages/payment-method-support#supported-tokens) for all asset identifiers. | | `outputs` | Asset\[] | Assets the user receives in the `destinationAddress`. | | `funders` (optional) | [FunderRole](#funderrole)\[] | Funding wallets available to the user at payment time. | | `destinationAddress` | string | The address where deposited funds are sent. | ## Withdrawal The following are configuration options for the withdraw widget. This is used for withdrawing assets from the user's wallet or an in-app smart contract wallet to another address. | Name | Type | Description | | :------------------------------ | :------------------------ | :-------------------------------------------------------------------------------------------------------------------------- | | `inputs` (optional) | Asset\[] | Assets that can be withdrawn. Defaults to `deposit.outputs` minus fiat assets at open time. Pass `[]` to accept all assets. | | `outputs` (optional) | Asset\[] | Filter on allowed withdrawal targets. | | `funder` | [FunderRole](#funderrole) | The wallet funding the withdrawal. Required — `openWithdrawal()` throws if it is not set. | | `destinationAddress` (optional) | string | The offramp target address. This can be on another chain than the origin address. | ## FunderRole Configuration options for each funding wallet with transaction capability. | Name | Type | Description | | :---------------------- | :--------------------- | :------------------------------------------ | | `getAddress` | () => Promise\ | Returns the funder's wallet address. | | `sendTransaction` | SendTransaction | Sends a transaction from the funder wallet. | | `walletName` (optional) | string | Display name shown in the widget. | ## CustomStyles All fields are optional. Colors are CSS color strings. | Name | Type | Description | | :------------------------------ | :------------------------ | :-------------------------------------------------------------------------------------------------------------------------- | | `primaryColor` (optional) | string | Primary accent color. | | `backgroundColor` (optional) | string | Widget background color. | | `borderColor` (optional) | string | Border color. | | `textColor` (optional) | string | Primary text color. | | `textSecondaryColor` (optional) | string | Secondary text color. | | `accentColor` (optional) | string | Accent color. | | `successColor` (optional) | string | Color used for success states. | | `alertColor` (optional) | string | Color used for alerts/errors. | | `borderStyle` (optional) | `"SQUARE"` \| `"DEFAULT"` | Corner style of widget components. | | `backgroundStyle` (optional) | `"BLUR"` \| `"OFF"` | Modal backdrop style. | | `componentShadow` (optional) | string | CSS box-shadow string applied to widget components. | | `zIndex` (optional) | number | Stacking level of the widget interface when it is in modal mode. No default. Raise it above all other elements on the page. | ## Type Definitions ```ts theme={null} type Address = string; type TypedData = string; interface TransactionRequest { to: string; from?: string; nonce?: number; gasLimit?: bigint; gasPrice?: bigint; maxPriorityFeePerGas?: bigint; maxFeePerGas?: bigint; data?: string; value?: 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; } interface EVMChainConfig { chain_id: bigint; network: string; explorer?: string; image?: string; is_testnet: boolean; address_family: "EVM"; native_currency: { name: string; symbol: string; decimals: number; }; rpc?: string; } interface CustomStyles { primaryColor?: string; backgroundColor?: string; borderColor?: string; textColor?: string; textSecondaryColor?: string; accentColor?: string; componentShadow?: string; borderStyle?: "SQUARE" | "DEFAULT"; backgroundStyle?: "OFF" | "BLUR"; successColor?: string; alertColor?: string; zIndex?: number; } type FontName = "haffer" | "inter" | "eb-garamond" | "roboto-mono"; type HeaderTitle = string; type Asset = string; // Format: "chain:tokenAddress" type SignMessage = (input: { message: string; ownerAddress?: Address }) => Promise; type SignTypedData = (input: { typedData: TypedData; ownerAddress?: Address }) => Promise; type SendTransaction = ( transaction: TransactionRequest, chainConfig: EVMChainConfig, ) => Promise; type WalletAuthChain = "EVM" | "SOL" | "SUI"; type SignAuthMessage = (input: { message: string; address: Address; walletType: WalletAuthChain; }) => Promise; interface FunderRole { getAddress: () => Promise
; sendTransaction: SendTransaction; walletName?: string; } type Owner = | { type?: "direct"; getAddress: () => Promise
; signMessage: SignMessage; signTypedData: SignTypedData; sendTransaction?: SendTransaction; walletName?: string; // e.g. MetaMask } | { type: "wallet-auth"; walletType: WalletAuthChain; address: Address; signAuthMessage: SignAuthMessage; } | { type: "otp-auth"; address: string; // email address }; interface DepositConfig { inputs?: Asset[]; outputs?: Asset[]; funders?: FunderRole[]; destinationAddress?: Address; } interface WithdrawalConfig { inputs?: Asset[]; outputs?: Asset[]; funder?: FunderRole; destinationAddress?: Address; } interface HallidayPaymentsConfig { apiKey: string; owner?: Owner; onConnectWallet?: () => void; deposit?: DepositConfig; withdrawal?: WithdrawalConfig; customStyles?: CustomStyles; fontName?: FontName; headerTitle?: HeaderTitle; targetElementId?: string; } interface DepositSession { input?: { asset: Asset; amount?: string; }; output?: Asset; inputFiatValue?: { currency: string; amount: string; }; fundingAddress?: string; destination?: string; locked?: boolean; } interface WithdrawSession { input?: { asset: Asset; amount?: string; }; output?: Asset; inputFiatValue?: { currency: string; amount: string; }; destination?: string; locked?: boolean; } type OrderStatus = any; interface OrderNotification { paymentId: string; issue: string; message: string; } type HallidayEvent = "status" | "error" | "close"; type HallidayErrorSource = "preload" | "openDeposit" | "openWithdrawal" | "resolution" | "load"; type HallidayRuntimeError = Error & { source: HallidayErrorSource }; type HallidayEventHandler = E extends "status" ? (s: { type: string; payload: OrderStatus }) => void : E extends "error" ? (e: HallidayRuntimeError) => void : () => void; interface HallidaySnapshot { isReady: boolean; isOpen: boolean; status: { type: string; payload: OrderStatus } | null; error: HallidayRuntimeError | null; notifications: OrderNotification[]; } ``` ## Usage Patterns ### Config and Wallet Updates After initialization, the instance's `updateConfig` function can be used to pass more parameters or overwrite previously passed parameters. The React SDK has conventional React state management functions, so `updateConfig` should not be used. Properties should be used to update config settings on the `HallidayPaymentsProvider`. For wallet changes, the React SDK has the `updateWallets` function returned by the `useHallidayPayments` hook. ```tsx copy theme={null} // Action for use in useEffect block updateWallets({ owner: { type: "...", address: "..." }, deposit: { funders: [{ getAddress, sendTransaction, walletName: "MetaMask" }], destinationAddress: "0xDest...", }, withdrawal: { funder: { getAddress, sendTransaction }, destinationAddress: "0xDest...", }, }); ``` ```tsx copy theme={null} halliday.updateConfig({ // ... owner: { getAddress: async () => "0x...", signMessage: async ({ message }) => "...", signTypedData: async ({ typedData }) => "...", sendTransaction: async (tx, chain) => TransactionReceipt, walletName: "Bob's Account", // Optional, shown in the wallet selector }, // ... }); ``` ### Ready & event handlers The Halliday instance monitors events that an event handler can be registered for. ```tsx copy theme={null} import { useEffect } from "react"; import { useHallidayPayments } from "@halliday-sdk/payments/react"; function HallidayEventLogger() { const { instance } = useHallidayPayments(); useEffect(() => { const offStatus = instance.on("status", (s) => console.log(`status: ${s.type}`)); const offError = instance.on("error", (e) => console.log(`error (${e.source}): ${e.message}`) ); const offClose = instance.on("close", () => console.log("widget closed")); return () => { offStatus(); offError(); offClose(); }; }, [instance]); return null; } ``` ```tsx copy theme={null} const halliday = new HallidayPayments({/* ... */}); halliday.on("status", (s) => console.log(`status: ${s.type}`)); halliday.on("error", (e) => { console.log(`error (${e.source}): ${e.message}`); }); halliday.on("close", () => console.log("widget closed")); ``` There is a promise returned by the `ready` function. The promise resolves with no value once the initialization of the Halliday widget is completed. If there is an error in initialization a `WidgetLoadError` will be passed to the rejection. ```js theme={null} await halliday.ready(); ``` #### iframe postMessage events The Halliday SDK's iframe will pass messages through the `window.postMessage()` function. Integrations of the Halliday widget do not require monitoring of these events. The following example illustrates logging of these events for debugging purposes. ```js theme={null} window.addEventListener("message", (event) => { if (event.origin !== "https://app.halliday.xyz") return; const { type, payload } = event.data || {}; console.log("Halliday iframe message", type, payload); }); ``` ### Using the wallet connector Halliday can prompt users to choose a wallet to connect to the app. The wallet connector provides many options including MetaMask, Coinbase Wallet, Rainbow, 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} createRoot(document.getElementById("root")).render( ); ``` ```tsx copy theme={null} import HallidayPayments from "@halliday-sdk/payments"; const halliday = new HallidayPayments({ apiKey: HALLIDAY_PUBLIC_API_KEY, deposit: { outputs: ["base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"], destinationAddress: smartAccountAddress, }, }); ``` ### Using a connected wallet The Halliday JS 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 is available to Halliday JS 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 ```tsx copy theme={null} import { useState } from "react"; import { createRoot } from "react-dom/client"; import { HallidayPaymentsProvider, useHallidayPayments } from "@halliday-sdk/payments/react"; import { connectWalletClient } from "@halliday-sdk/payments/viem"; import { createWalletClient, custom } from "viem"; import { base } from "viem/chains"; export default function App() { const { openDeposit, updateWallets, isReady } = useHallidayPayments(); const [ address, setAddress ] = useState(null); const connect = async () => { const _ethereum = window.ethereum || window.phantom.ethereum; if (!_ethereum) { alert("Wallet is missing."); return; } setAddress(...await _ethereum.request({ method: "eth_requestAccounts" })); const owner = connectWalletClient(() => createWalletClient({ chain: base, transport: custom(_ethereum), }), ); updateWallets({ owner, deposit: { funders: [], destinationAddress: address, }, }); }; return (

Halliday Hello World

); } createRoot(document.getElementById("root")).render( ); ```
```tsx copy theme={null} import HallidayPayments from "@halliday-sdk/payments" import { connectWalletClient } from "@halliday-sdk/payments/viem"; import { createWalletClient, custom } from "viem"; import { base } from "viem/chains"; const [ address ] = await window.ethereum.request({ method: "eth_requestAccounts" }); const owner = connectWalletClient(() => createWalletClient({ chain: base, transport: custom(window.ethereum), }), ); const halliday = new HallidayPayments({ apiKey: HALLIDAY_PUBLIC_API_KEY, owner, deposit: { // USDC on Base outputs: [ "base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913" ], funders: [], destinationAddress: address, }, }); // More or updated config parameters can be passed later // using `updateConfig` e.g. add another wallet connection halliday.updateConfig({}); ```
#### Wagmi ```tsx copy theme={null} import { useEffect } from "react"; import { useHallidayPayments } from "@halliday-sdk/payments/react"; import { connectWalletClient } from "@halliday-sdk/payments/viem"; import { useAccount, useWalletClient } from "wagmi"; import { useConnectModal, useAccountModal } from "@rainbow-me/rainbowkit" export default function App() { const { openDeposit, updateWallets, isReady } = useHallidayPayments(); const { address, isConnected } = useAccount(); const { data: walletClient } = useWalletClient() const { openConnectModal } = useConnectModal() const { openAccountModal } = useAccountModal() const enabled = isConnected && !!walletClient && isReady const rainbowkit = openConnectModal || openAccountModal useEffect(() => { const owner = connectWalletClient(() => walletClient); updateWallets({ owner, deposit: { funders: [], destinationAddress: address, }, }); }, [enabled, walletClient, address]); return (
); } ```
#### Ethers.js ```tsx copy theme={null} import React, { useState } from "react"; import { useHallidayPayments } from "@halliday-sdk/payments/react"; import { connectSigner } from "@halliday-sdk/payments/ethers"; import { BrowserProvider } from "ethers"; export default function App() { const { openDeposit, updateWallets, isReady } = useHallidayPayments(); const [address, setAddress] = useState(null); const connect = async () => { const _ethereum = window.ethereum || window.phantom.ethereum; if (!_ethereum) { alert("Wallet is missing."); return; } setAddress(...await _ethereum.request({ method: "eth_requestAccounts" })); const owner = connectSigner(() => { return new BrowserProvider(_ethereum).getSigner() }); updateWallets({ owner, deposit: { funders: [], destinationAddress: address, }, }); }; return (
); } ```
```tsx copy theme={null} import HallidayPayments from "@halliday-sdk/payments" import { connectSigner } from "@halliday-sdk/payments/ethers"; import { BrowserProvider } from "ethers"; const [ address ] = await window.ethereum.request({ method: "eth_requestAccounts" }); const owner = connectSigner(() => new BrowserProvider(window.ethereum).getSigner(), ); const halliday = new HallidayPayments({ apiKey: HALLIDAY_PUBLIC_API_KEY, owner, deposit: { // USDC on Base outputs: [ "base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913" ], funders: [], destinationAddress: address, }, }); // More or updated config parameters can be passed later // using `updateConfig` e.g. add another wallet connection halliday.updateConfig({}); ```
#### Dynamic The following code example is a demonstration of passing a Dynamic embedded wallet provider to the Halliday JS SDK in a full React app which is available in the [SDK example apps section](/pages/payments-sdk-example-apps). ```tsx copy theme={null} import { useEffect } from "react"; import { useHallidayPayments } from "@halliday-sdk/payments/react"; import { connectWalletClient } from "@halliday-sdk/payments/viem"; import { useAccount, useWalletClient } from "wagmi"; import { useDynamicContext } from "@dynamic-labs/sdk-react-core"; export default function App() { const { primaryWallet, setShowAuthFlow, handleLogOut } = useDynamicContext(); const { openDeposit, updateWallets, isReady } = useHallidayPayments(); const { address, isConnected } = useAccount(); const { data: walletClient } = useWalletClient(); const enabled = primaryWallet && isConnected && walletClient && isReady; const connect = () => { if (primaryWallet) handleLogOut(); else setShowAuthFlow(true); } useEffect(() => { if (!enabled) return; const owner = walletClient ? connectWalletClient(() => walletClient) : null; updateWallets({ owner, deposit: { funders: [], destinationAddress: address }, }); }, [enabled, walletClient, address]); return (
); } ```
#### Privy In addition to the following code example, a demonstration of passing a connected Privy wallet to the Halliday JS SDK in a full React app is available in the [SDK example apps section](/pages/payments-sdk-example-apps). ```tsx copy theme={null} import React, { useState } from "react"; import { usePrivy, useWallets } from "@privy-io/react-auth" import { useHallidayPayments } from "@halliday-sdk/payments/react"; import { connectSigner } from "@halliday-sdk/payments/ethers"; import { BrowserProvider } from "ethers"; export default function App() { const { ready, authenticated, login, logout } = usePrivy(); const { wallets } = useWallets(); const { openDeposit, updateWallets, isReady } = useHallidayPayments(); const [address, setAddress] = useState(null); const wallet = wallets.find(w => w.walletClientType === "privy"); if (wallet && !address) { setAddress(wallet.address); } const enabled = wallet && !address; if (enabled) { wallet.getEthereumProvider().then((provider) => { const owner = connectSigner( () => new BrowserProvider(provider).getSigner(wallet.address) ); updateWallets({ owner, deposit: { funders: [], destinationAddress: address, }, }); }).catch(console.error); } return (
); } ```
#### Turnkey Turnkey embedded wallet signatures can be integrated with the Halliday JS SDK using the `@turnkey/react-wallet-kit` and `@turnkey/viem` SDKs with `viem` and `@halliday-sdk/payments`. The developer is required to create an interface for the Turnkey SDK. A full React.js code example is available here: [Halliday JS SDK Turnkey React.js Example](https://github.com/HallidayInc/HallidaySdkTurnkeyReactExample). ### Multiple Funders In the scenario that a developer chooses to show multiple possible funding sources for the user, an array of funder objects can be passed as `deposit.funders` to the `HallidayPayments` constructor (or later via `updateConfig` / `updateWallets`). Funders can be any address that is expected to fund a payment. The SDK widget will take account of token balances at the time of a payment so the user can select a possible input token and amount. **Naming Each Funder** To differentiate funders in the UI, a name string can be supplied that will be shown to the user. Pass `walletName` as a member of each funder object. ```tsx copy theme={null} // First, create connections to multiple funding source wallets, like shown earlier owner.walletName = "Alice's Cool MetaMask Wallet"; privyWallet.walletName = "Alice's Fun Privy Wallet"; updateWallets({ owner, deposit: { funders: [ owner, // User's MetaMask or the like privyWallet, // Embedded wallet that the user is signed into with a balance // More funders can go here ] } }); ``` ```tsx copy theme={null} // First, create connections to multiple funding source wallets, like shown earlier owner.walletName = "Alice's Cool MetaMask Wallet"; privyWallet.walletName = "Alice's Fun Privy Wallet"; const halliday = new HallidayPayments({ apiKey: HALLIDAY_PUBLIC_API_KEY, owner, deposit: { funders: [ owner, // User's MetaMask or the like privyWallet, // Embedded wallet that the user is signed into with a balance // More funders can go here ] } }); ```
### Embedding the SDK Widget By default, the Payments Widget opens as a modal overlaying the web page. Another option is to embed it within a page. In React, render the `` component where the widget should appear. In JavaScript, provide the `targetElementId` option. ```tsx copy theme={null} import { HallidayPaymentsProvider, HallidayEmbed } from "@halliday-sdk/payments/react"; ``` ```tsx copy theme={null} import HallidayPayments from "@halliday-sdk/payments"; const halliday = new HallidayPayments({ apiKey: HALLIDAY_PUBLIC_API_KEY, // Embed the widget inside an HTML element by id targetElementId: "element-id", deposit: { outputs: ["base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"], }, }); ``` ### Customizing styles Halliday supports setting custom styles on the Payments Widget in order to match an application's existing user interface. ```tsx copy theme={null} const customStyles = { primaryColor: "#66ff66", // Button (labeled in diagram below) backgroundColor: "#FFFFFF", // Background borderColor: "rgba(255, 255, 255, 1)", // #FFFFFF Border textColor: "#ff0000", // Text textSecondaryColor: "rgb(204, 51, 255)", // #CC33ff "Secondary text" accentColor: "#33cccc", // Accent componentShadow: "2px 5px #e6e6e6", borderStyle: "SQUARE", // or undefined for default backgroundStyle: "OFF", // or BLUR (uppercase only) successColor: "#ff6600", alertColor: "#ffff00", zIndex: 1000, // number only }; ``` ```tsx copy theme={null} import HallidayPayments from "@halliday-sdk/payments"; const halliday = new HallidayPayments({ apiKey: HALLIDAY_PUBLIC_API_KEY, owner, deposit: { outputs: ["base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"], }, customStyles: { primaryColor: "#66ff66", // Button (labeled in diagram below) backgroundColor: "#FFFFFF", // Background borderColor: "rgba(255, 255, 255, 1)", // #FFFFFF Border textColor: "#ff0000", // Text textSecondaryColor: "rgb(204, 51, 255)", // #CC33ff "Secondary text" accentColor: "#33cccc", // Accent componentShadow: "2px 5px #e6e6e6", borderStyle: "SQUARE", // or undefined for default backgroundStyle: "OFF", // or BLUR (uppercase only) successColor: "#ff6600", alertColor: "#ffff00", zIndex: 1000, // number only }, fontName: "haffer", // or "inter", "eb-garamond", "roboto-mono" headerTitle: "Checkout", // ... }); ``` The following diagram shows how these values are used:
Colors in the above code and diagram:
For further customization, please [contact the Halliday team](mailto:partnerships@halliday.xyz). Building a completely custom user interface is possible by implementing the [Halliday API](/pages/halliday-api-docs), instead of the Halliday JS SDK. # Payments SDK Example Apps Source: https://docs.halliday.xyz/pages/payments-sdk-example-apps The following is an open source example app that demonstrates how to build onramping and swapping using the Payments SDK widget. * [Halliday JS SDK examples repository](https://github.com/HallidayInc/HallidayPaymentsSdkExamples) React.js with Wagmi and Rainbowkit. * [Halliday SDK example with Wagmi and Rainbowkit repository](https://github.com/HallidayInc/HallidaySdkViemWagmiRainbowkitExample) The following React.js example apps use Dynamic's `@dynamic-labs/sdk-react-core` SDK in addition to the Halliday JS SDK to integrate a Dynamic embedded wallet for use with the Halliday JS SDK widget. * [Halliday JS SDK example with Dynamic & Wagmi repository](https://github.com/HallidayInc/HallidaySdkDynamicWagmi) * [Halliday JS SDK example with Dynamic & Ethers.js 6 repository](https://github.com/HallidayInc/HallidaySdkDynamicEthers) The following React.js example app uses Privy's `@privy-io/react-auth` SDK in addition to the Halliday JS SDK to integrate a Privy wallet for use with the Halliday JS SDK widget. * [Halliday JS SDK example with Privy Wallet repository](https://github.com/HallidayInc/HallidaySdkPrivyReactExample) The following React.js example app uses Turnkey embedded wallets with the Halliday JS SDK widget. * [Halliday JS SDK example with Turnkey embedded wallets repository](https://github.com/HallidayInc/HallidaySdkTurnkeyReactExample) # React Native Example Apps Source: https://docs.halliday.xyz/pages/react-native-example-apps These open source example apps demonstrate integrating Halliday into a React Native mobile app with [Expo](https://expo.dev/). The Halliday JS SDK or API can be used in a mobile interface with [Reown](https://reown.com/) (formerly WalletConnect) for wallet connections. The featured projects were tested with builds for iOS and Android. ## React Native Halliday JS SDK Integration This open source example uses the Halliday JS SDK to implement the SDK widget inside a web view. This project implements Reown, Expo, and Ethers.js. * [React Native Halliday JS SDK Example App](https://github.com/HallidayInc/HallidaySdkReactNative)
## React Native Halliday API Integration This open source example implements Halliday using the API directly. Dependencies include Reown, Expo, and Wagmi. For embedded wallet support, Dynamic can be used to connect a wallet via email address. * [React Native Halliday API Example App](https://github.com/HallidayInc/HallidayApiReactNative)
# Deposits with the Halliday JS SDK Source: https://docs.halliday.xyz/pages/sdk-deposits The deposit modal is the primary screen of the Halliday SDK widget. Once the SDK is [initialized](/pages/payments-sdk-docs#initialization), the deposit modal can be opened at any time using the `openDeposit` function. The `openDeposit` function is meant to be called inside of a button click event handler. When the user is ready to make a deposit, they will click or tap a button in the app user interface, which will trigger the deposit modal. For complete example implementations of opening the Halliday SDK deposit widget, see the [Payments SDK Example Apps](/pages/payments-sdk-example-apps) page. ## Configuring the Deposit options [Deposit configuration options](/pages/payments-sdk-docs#deposit) are part of the Halliday config which is documented on the widget documentation page. ### React With the React SDK, the constant deposit options can be defined in the properties of a `HallidayPaymentsProvider`. ```jsx theme={null} ``` Connected wallet objects can be passed using the `updateWallets` function after a user connects their wallet to the app. ```jsx theme={null} const owner = { getAddress: async () => "0x...", // required, async signMessage: async ({ message }) => "...", // required signTypedData: async ({ typedData }) => "...", // required sendTransaction: async (tx, chain) => TransactionReceipt, // Optional walletName: "Bob's Account", // Optional, shown in the wallet selector } // Action for use in useEffect block updateWallets({ owner, deposit: { funders: [ owner, { getAddress, sendTransaction, walletName: "MetaMask" } ], destinationAddress: "0xDest...", }, }); ``` ### JavaScript Constant deposit options can be passed to the constructor of a `HallidayPayments` object or passed later using the `updateConfig` function. Partial configurations are valid for either function. ```js theme={null} const halliday = new HallidayPayments({ apiKey: HALLIDAY_PUBLIC_API_KEY, deposit: { outputs: ["base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"], destinationAddress: smartAccountAddress, }, }); const owner = { getAddress: async () => "0x...", // required, async signMessage: async ({ message }) => "...", // required signTypedData: async ({ typedData }) => "...", // required sendTransaction: async (tx, chain) => TransactionReceipt, // Optional walletName: "Bob's Account", // Optional, shown in the wallet selector } halliday.updateConfig({ owner, deposit: { funders: [ owner, { getAddress, sendTransaction } ], destinationAddress: "0xDest...", }, }); ``` ## Opening the Deposit widget To display the deposit widget, use the `openDeposit` function. ```tsx copy theme={null} import { HallidayPaymentsProvider, useHallidayPayments } from "@halliday-sdk/payments/react"; // ... const { openDeposit, updateWallets, isReady } = useHallidayPayments(); // ... ``` ```tsx copy theme={null} const halliday = new HallidayPayments({ /* ... */}); // ... button.addEventListener("click", halliday.openDeposit); ``` ### Deposit session parameters A deposit session can optionally have values passed to it such as a preset input asset and amount, a preset output asset, a funding address, or a destination address. If the developer does not want for the user to be able to modify the asset setting or input amount, they can lock the session to the developer's chosen settings. An `input` will take precedent over the `inputFiatValue` settings. ```js theme={null} openDeposit({ input: { asset: "base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", amount: "50", }, output: "base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", inputFiatValue: { currency: "USD", amount: "50", }, fundingAddress: "0xFunder...", destination: "0xDest...", locked: true, }); ``` See the [type definitions](/pages/payments-sdk-docs#type-definitions) on the SDK documentation page for more information. # User Transaction History in the SDK Source: https://docs.halliday.xyz/pages/sdk-transaction-history To show the past Halliday payment activity of the account in the widget, the user can click or tap the icon at the top right of the widget. This opens the **Transactions & activity** page of the widget. To directly open this screen in the widget, use the `openActivity` method. ```tsx copy theme={null} const { openActivity } = useHallidayPayments(); // Must be called after openDeposit()/openWithdrawal() ``` ```tsx copy theme={null} const halliday = new HallidayPayments({ /* ... */}); // ... button.addEventListener("click", halliday.openActivity); ``` # Withdrawals with the Halliday JS SDK Source: https://docs.halliday.xyz/pages/sdk-withdrawals ## Withdrawal Widget For apps that define an account for the user, like an embedded wallet or smart contract wallet, the withdraw widget allows the user to move assets out of their account. The destination of a withdrawal can be any valid address like the user's EOA or a centralized exchange account deposit address. The destination also can be a different asset on a different chain from the origin. E.g. withdraw pUSD from a user's prediction market account on Polygon to an EOA on MegaETH as USDC, executed in one user interaction. In practice, the withdrawal widget (`openWithdrawal`) is opened by a different button in the app user interface than the standard deposit. For complete example implementations of opening the Halliday SDK withdrawal widget, see the [Payments SDK Example Apps](/pages/payments-sdk-example-apps) page. Before the withdraw button is clicked, the withdraw configuration must be set. ## Configuring the Withdrawal options [Withdrawal configuration options](/pages/payments-sdk-docs#withdrawal) are part of the Halliday config which is documented on the widget documentation page. ### React With the React SDK, the constant withdraw options can be defined in the properties of a `HallidayPaymentsProvider`. ```jsx theme={null} ``` A connected wallet funder object can be passed using the `updateWallets` function after a user connects their wallet to the app. ```jsx theme={null} // Action for use in useEffect block updateWallets({ withdrawal: { inputs: [ "base:0x" ], // Optional, defaults to deposit.outputs outputs: [ "base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913" ], funder: { getAddress, sendTransaction, walletName: "MetaMask" }, destinationAddress: "0x...", }, }); ``` ### JavaScript Constant withdraw options can be passed to the constructor of a `HallidayPayments` object or passed later using the `updateConfig` function. Partial configurations are valid for either function. ```js theme={null} const halliday = new HallidayPayments({ apiKey: HALLIDAY_PUBLIC_API_KEY, deposit, withdrawal: { inputs: [ "base:0x" ], // Optional, defaults to deposit.outputs outputs: [ "base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913" ], funder: { getAddress, sendTransaction, walletName: "MetaMask" }, destinationAddress: "0x...", }, }); // Also can be modified after initialization halliday.updateConfig({ withdrawal: { inputs: [ "base:0x" ], // Optional, defaults to deposit.outputs outputs: [ "base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913" ], funder: { getAddress, sendTransaction, walletName: "MetaMask" }, destinationAddress: "0x...", }, }); ``` ## Opening the Withdrawal widget To display the withdrawal widget, use the `openWithdrawal` function. ```tsx copy theme={null} import { HallidayPaymentsProvider, useHallidayPayments } from "@halliday-sdk/payments/react"; // ... const { openWithdrawal } = useHallidayPayments(); // ... ``` ```tsx copy theme={null} const halliday = new HallidayPayments({ /* ... */}); // ... button.addEventListener("click", halliday.openWithdrawal); ``` ### Withdrawal session parameters A withdrawal session can optionally have values passed to it such as a preset input asset and amount, a preset output asset, or a destination address. If the developer does not want for the user to be able to modify the asset setting or input amount, they can lock the session to the developer's chosen settings. An `input` will take precedent over the `inputFiatValue` settings. ```js theme={null} openWithdrawal({ input: { asset: "base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", amount: "50", }, output: "base:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", inputFiatValue: { currency: "USD", amount: "50", }, destination: "0xDest...", locked: true, }); ``` See the [type definitions](/pages/payments-sdk-docs#type-definitions) on the SDK documentation page for more information. # Swift & Kotlin Example Apps Source: https://docs.halliday.xyz/pages/swift-kotlin-native-mobile-example-apps These open source example apps demonstrate integrating Halliday on mobile with Swift for iOS or Kotlin for Android. The featured projects were tested with builds for iOS and Android. ## Halliday JS SDK integration with a self-hosted private key These open source example apps use the Halliday JS SDK inside a mobile web view in a native Swift and Kotlin app. They implement interfacing with a self-hosted private key and signer within the mobile app source code. Note that the private key management examples are **not audited** and are for **demonstration purposes only**. ## Kotlin for Android Example * [Halliday JS SDK Kotlin example with a self-hosted private key](https://github.com/HallidayInc/HallidaySDKKotlinSHPK)
## Swift for iOS Example * [Halliday JS SDK iOS example with a self-hosted private key](https://github.com/HallidayInc/HallidaySDKSwiftSHPK)
# Webhooks Source: https://docs.halliday.xyz/pages/webhooks Webhooks let Halliday notify your backend when a payment workflow finishes, so you don't have to poll for status. You register an HTTPS endpoint once, and Halliday sends it a signed `POST` request whenever a subscribed event occurs. ## When webhooks are sent A webhook fires when a workflow reaches a terminal state: | Event type | Sent when the workflow status becomes | | -------------------- | ------------------------------------- | | `WORKFLOW_COMPLETED` | `COMPLETE` | | `WORKFLOW_FAILED` | `FAILED` | You choose which of these events to subscribe to when you register the webhook. Each delivery is an HTTP `POST` with a JSON body and an `X-Halliday-Signature` header you can use to verify it came from Halliday. ## Authentication Managing webhooks requires your **secret API key** (`sk_...`), not a public key (`pk_...`). Public keys cannot manage webhooks. Your secret key is generated in the [Halliday dashboard](https://dashboard.halliday.xyz/) at the moment you create a key set. **It is shown only once, right after you generate the key set — the dashboard never displays it again.** Copy it somewhere safe when you create the key set; if you lose it, you will need to generate a new key set. Pass the secret key as a bearer token: ``` Authorization: Bearer sk_HALLIDAY_SECRET_KEY_HERE ``` ## Create a webhook using the API 1. **Get your secret key.** Generate a key set in the [dashboard](https://dashboard.halliday.xyz/) and copy the secret key (`sk_...`) the one time it is shown. 2. **Register your endpoint.** Send a `POST` to `/orgs/webhooks` with the URL Halliday should call, a unique `label`, and the events you want to receive: ```bash theme={null} curl -X POST https://v2.prod.halliday.xyz/orgs/webhooks \ -H "Authorization: Bearer sk_HALLIDAY_SECRET_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "url": "https://api.yourapp.com/halliday/webhooks", "label": "prod-workflows", "event_types": ["WORKFLOW_COMPLETED", "WORKFLOW_FAILED"] }' ``` 3. **Save the signing secret.** The response includes a `signing_secret`. Like your API key, it is returned only on creation (and rotation), so store it now. Use it to verify the `X-Halliday-Signature` header on each delivery. ```json theme={null} { "id": "4f2c8c1a-1b2c-4d3e-8f5a-6b7c8d9e0f12", "label": "prod-workflows", "url": "https://api.yourapp.com/halliday/webhooks", "signing_secret": "a1b2c3…" } ``` 4. **Acknowledge deliveries.** When Halliday `POST`s an event to your URL, respond with any `2xx` status within 10 seconds. Slow or failed deliveries are retried. For full request and response details, signature verification, and the other management endpoints (list, update, rotate secret, delete), see the [Webhooks API reference](/api-reference/webhooks/register-a-webhook).