Test mode
Build and test a full LayerHop integration without real funds. lh_test_ keys return sandbox quotes and placeholder transactions, run a simulated order lifecycle in about 10 seconds, send webhooks to test endpoints, and use magic amounts for refunds, failures and no-route errors.
Test mode lets you build and test a complete LayerHop integration, from quote to filled order and webhook, without real funds or a real wallet. Requests made with an lh_test_… key are served by the sandbox provider: quotes are realistic (priced from live USD prices), built transactions are placeholders that cannot move funds, and reported orders run a simulated lifecycle that fills in about 10 seconds. Test data is kept apart from live data everywhere, and it never counts towards volume or partner earnings.
Get a test key#
- Sign in to dashboard.layerhop.com and turn on Test mode (the switch in the header).
- Open API keys and choose Create API key. In test mode the key starts with
lh_test_. - Send it as
x-api-key, exactly like a live key. Nothing else in your code changes.
You can also try every step below in the API playground: paste your test key and follow the Next step buttons.
Live key lh_live_… |
Test key lh_test_… |
|
|---|---|---|
| Quotes | Every real provider | The sandbox provider only, priced from live USD prices |
POST /v1/orders |
The real transaction for the user's wallet | A placeholder with tx.sandbox: true that moves no funds |
POST /v1/simulate |
Simulated on chain | Always ok: true, nothing is sent to an RPC |
| Order tracking | Provider status and chain receipts | Simulated: pending, then the outcome about 10 s later |
| Webhooks | Live endpoints | Test endpoints only |
| Dashboard data | Live view | Test view (toggle Test mode) |
| Volume and partner earnings | Counted | Never counted |
| Rate limit | 120 requests/minute by default | Same |
Run the whole flow#
export LAYERHOP_API_KEY=lh_test_… # your test key
# 1. Quote: the sandbox answers, quoteId starts with q_test_
curl -H "x-api-key: $LAYERHOP_API_KEY" \
"https://api.layerhop.com/v1/quote?srcChain=base&srcToken=USDC&amount=100000000&dstChain=solana&dstToken=SOL&sender=0x1111111111111111111111111111111111111111&recipient=9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM"
# 2. Build: returns a placeholder transaction (tx.sandbox: true)
curl -X POST -H "x-api-key: $LAYERHOP_API_KEY" -H "content-type: application/json" \
-d '{"quoteId":"q_test_…"}' https://api.layerhop.com/v1/orders
# 3. Report any well-formed transaction hash (0x + 64 hex characters for EVM chains)
curl -X POST -H "x-api-key: $LAYERHOP_API_KEY" -H "content-type: application/json" \
-d '{"txHash":"0x1111111111111111111111111111111111111111111111111111111111111111"}' \
https://api.layerhop.com/v1/orders/q_test_…/submitted
# 4. Track it: pending at first, filled about 10 seconds later
curl -H "x-api-key: $LAYERHOP_API_KEY" \
https://api.layerhop.com/v1/orders/sandbox/0x1111111111111111111111111111111111111111111111111111111111111111With the TypeScript SDK the only change is the key:
import { LayerHop } from "@layerhop/sdk";
const lh = new LayerHop({ apiKey: process.env.LAYERHOP_API_KEY }); // lh_test_…
const { best } = await lh.quote({
srcChain: "base", srcToken: "USDC", amount: "100000000",
dstChain: "solana", dstToken: "SOL",
sender: "0x1111111111111111111111111111111111111111",
recipient: "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
});
const tx = await lh.buildTx(best.quoteId); // tx.sandbox === true: do not send it
await lh.reportSubmitted(best.quoteId, "0x" + "11".repeat(32));Magic amounts#
The last digits of amount choose how a test order ends, so you can exercise every path of your integration:
amount ends in |
Result |
|---|---|
4001 |
The order is refunded (order.refunded webhook) |
4002 |
The order fails (order.failed webhook) |
4004 |
GET /v1/quote returns NO_ROUTE |
| anything else | The order is filled (order.filled webhook) with a generated destination transaction hash |
For example amount=100004001 quotes 100.004001 USDC and ends in a refund.
Order lifecycle and webhooks#
After you report a hash, LayerHop's worker picks up the test order like any other:
- First check, within a few seconds:
submitted→pending, and anorder.submittedwebhook. - Second check, about 10 seconds later:
pending→filled,refundedorfailed, and the matching webhook.
Test-mode events are sent only to webhook endpoints created in test mode, and live events only to live endpoints, so a test order can never reach your production handler. Every event carries livemode (false for test events), and so does every order:
{
"id": "evt_…",
"type": "order.filled",
"livemode": false,
"createdAt": "2026-10-18T10:00:12.000Z",
"data": { "order": { "quoteId": "q_test_…", "status": "filled", "provider": "sandbox", "livemode": false } }
}Create a test endpoint with Test mode on in the dashboard (or POST /v1/dashboard/webhooks with "mode": "test"), then verify signatures exactly as in Webhooks.
Keeping test and live apart#
- A quote, order or webhook endpoint of one mode is invisible to the other. Building a
q_test_quote with a live key (or no key) returnsQUOTE_NOT_FOUND, and the reverse too. GET /v1/orders?address=lists test orders for test keys and live orders for live keys or no key.- The dashboard's overview, usage, logs, orders, webhooks and earnings take
?mode=test(the Test mode switch sets it). - Placeholder transactions are safe by construction: EVM placeholders send 0 to
0x000000000000000000000000000000000000dEaDwith a short memo as calldata; Solana placeholders use an all-zero blockhash that no cluster accepts. Still, never broadcast them.
Going live#
Swap lh_test_ for an lh_live_ key. The responses have the same shape (with livemode: true), so code that passes in test mode works unchanged. Remember to create live webhook endpoints too, since test endpoints never receive live events.
Last updated