Skip to main content
By the end of this guide, you’ll have two endpoints that let Mesh fetch deposit addresses on demand during a Link session, so you can offer every token and network you support without pre-generating an address for each one.
Before you start
  • Your core Mesh deposit integration is working end-to-end (see Fetch a Link Token)
  • Your platform can generate a deposit address for a given user, network, and token through your own API or custodian
  • Your Mesh representative has enabled the JIT Config page in your Mesh dashboard. JIT goes live as soon as you save both endpoint URLs and a signing secret there (see Build your JIT endpoints)

Overview

In a standard deposit integration, every entry in toAddresses in the Link Token request includes an address. That means you need an address ready for every token/network pair before the session starts, which pushes many platforms to offer only a handful of options. With Just-in-Time (JIT) address fetching, you can leave address out of any toAddresses entry. When the user selects that token and network in Link, Mesh calls your endpoints to get the address on demand. Because address generation can take time on some networks (sometimes over a minute), Mesh uses an initiate-then-poll model: Mesh asks you to start generating, then polls until the address is ready.

Benefits

Offer every option you support

Pass the full array of tokens and networks you support in every Link session, without pre-generating an address for each one. Mesh highly recommends this: the more options a user has, the more likely they are to find the asset and network they hold, and the higher the chance they complete their deposit.

Keep the most common paths instant

JIT and pre-generated addresses work together in the same request. Entries that include an address work exactly as they do today; Mesh only calls your endpoints for entries without one. Users never wait on generation for the options you’ve already supplied.
Recommended: pass the addresses you already have, plus the most common ecosystemsJIT adds some latency the first time a user selects a token/network pair. To keep the experience fast for most users, include an address for:
  • Any address you’ve already generated for this user
  • The most commonly used ecosystems, such as Bitcoin, EVM networks, Solana, and XRPL
Then rely on JIT for the long tail.

How it works

  1. You request a Link Token with your full toAddresses array, omitting address for any pair you want Mesh to fetch on demand.
  2. The user selects a token and network in Link.
  3. If that entry has no address, Mesh calls your initiate endpoint with the userId, networkId, and symbol.
  4. You respond immediately: 202 Accepted if generation has started, or 200 OK with the address if it’s already available.
  5. Mesh polls your status endpoint about every 2–3 seconds until the address is ready or failed, or until the overall deadline you set on the JIT Config page (default about 3 minutes) passes.
  6. Mesh verifies your response signature and validates the address, then shows it to the user and continues the deposit.

Build your JIT endpoints

You expose two HTTPS endpoints. Both sit in the live deposit path, so treat them as production-critical infrastructure. Once they’re built, register both URLs, your signing secret, and your overall generation deadline in the Mesh dashboard under Company > JIT Config (dashboard.meshconnect.com/company/jitConfig). This config is for production only. Sandbox never calls your endpoints, so there’s no sandbox URL or secret to set (see Test your implementation).

1. Initiate endpoint (POST)

Mesh calls this endpoint when a user selects a token/network pair with no address. Start generating the address and respond immediately; don’t hold the request open while generation runs.
  • 202 Accepted: generation has started. Mesh begins polling your status endpoint.
  • 200 OK: the address is already available. Return the full ready payload (see Ready response below) and Mesh skips polling.
  • 200 OK with { "status": "failed", "reason": "..." }: generation can’t start. Mesh ends the attempt.
  • Any other status code is treated as a failed generation.
A 202 needs no body and no signature; Mesh doesn’t read it.
userId
The end-user identifier you passed in the Link Token request.
networkId
Mesh’s network UUID for the blockchain network the user selected.
symbol
The token ticker the user selected (eg. USDC, ETH, XRP).
transactionId
The transactionId you passed in transferOptions in the Link Token request, or an empty string if you didn’t pass one. You can optionally check that it maps to the given userId and reject stale requests, but sessions are valid for 1 hour, so don’t reject a transactionId that’s less than 1 hour old. Address generation and idempotency are always keyed on the combination of userId, networkId, and symbol, never on transactionId.

2. Status endpoint (GET)

Mesh polls this endpoint with the same userId, networkId, and symbol it sent to initiate, plus transactionId. Always return 200 OK with one of three statuses: pending, ready, or failed. A non-2xx status, a call over 5s, or a connection failure on any single poll ends the attempt; Mesh doesn’t keep polling after it.
Still generating:
Ready (also the body for a 200 OK on initiate):
Failed:

Ready response

status required
One of ready, pending, or failed.
userId required
Must exactly match the request.
networkId required
Must exactly match the request.
symbol required
Must exactly match the request.
address required
A valid deposit address for the given network and token.
addressTag optional required for memo-chain networks
The destination tag or memo for memo/tag networks (see Include tags for memo-chain networks). Omit it otherwise.
reason optional
A human-readable failure reason when status is failed.
Echo back the exact userId, networkId, and symbol. Mesh rejects any ready response where these don’t match the request, and the deposit can’t continue on that token/network.

Keep addresses idempotent

This is critical. Violations can result in lost funds.For a given userId + networkId + symbol, your endpoints must always return the same address and tag. Repeated initiate calls must never mint a new address, and a status poll must never trigger generation. If they do, the user may see one address while the transfer executes against another, sending funds to an address no one can access.
Recommended pattern:
  • On the first initiate for a userId + networkId + symbol, start generation and persist the result against that user.
  • On every later initiate or status call for the same combination, return the stored value.
  • Within a single Link session, Mesh keeps the first address it receives so every step of that session uses the same one. Mesh doesn’t reuse addresses across sessions: a new session calls initiate again, so your endpoint is the source of truth. To skip JIT for an address you’ve already generated, pass it in the next Link Token request.

Include tags for memo-chain networks

Many networks route deposits to the right account using a destination tag or memo, including XRP, XLM, Hedera, EOS, WAX, Casper, Mina, Injective, and Cosmos-based networks (such as Cosmos Hub, Osmosis, dYdX, Celestia, Crypto.org, and Kava). Always include addressTag in the ready response for these networks.
A missing addressTag on a memo chain is rejected. Without a tag, a deposit can land in your omnibus address with no way to attribute it to the user, so Mesh treats a missing tag on these networks as a failure.

Authenticate requests and responses

Mesh signs every request with HMAC-SHA256 using a single shared secret, and you sign every response with the same secret. Each direction uses its own prefix (jit-request:v1 and jit-response:v1), so a request signature can never be reused as a response signature.

Verify Mesh’s requests

Mesh sends three headers on every call to both endpoints: To verify a request:
  1. Build the signature input by joining these parts with a newline (\n): jit-request:v1, {METHOD}, {path}, {sorted-query-string}, {sha256-of-body}, {X-Mesh-Timestamp}, {X-Mesh-Nonce}.
  2. Compute hex(HMAC-SHA256(shared_secret, signature_input)) and compare it to X-Mesh-Signature.
  3. Reject the request (any non-200 response) if the signature doesn’t match, or if X-Mesh-Timestamp is more than 60 seconds from your current time.
Canonicalization rules (you must produce these exact bytes):
  • {METHOD}: uppercase, such as POST or GET.
  • {path}: the path of the URL you registered, verbatim, including any base path. If a proxy or gateway rewrites the path before it reaches your code, verify against the registered path.
  • {sorted-query-string}: decode each query key and value, sort the pairs by key and then by value in plain byte (ordinal) order, then percent-encode each key and value per RFC 3986 (only A-Z a-z 0-9 - . _ ~ stay unencoded; a space becomes %20) and join them as k=v pairs with &. Empty for initiate, which has no query.
  • {sha256-of-body}: lowercase hex SHA-256 of the raw body bytes. Status polls have no body, so they hash the empty string: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.
  • {X-Mesh-Timestamp}: Unix seconds as a base-10 integer.

Sign your responses

Add two headers to every 200 response:
  • X-Mesh-Response-Timestamp: the current Unix timestamp in seconds
  • X-Mesh-Signature: hex(HMAC-SHA256(shared_secret, "jit-response:v1\n" + sha256(response_body) + "\n" + X-Mesh-Response-Timestamp))
Mesh rejects any response with a missing or invalid signature and doesn’t use the address. Mesh also rejects a response whose X-Mesh-Response-Timestamp is more than 60 seconds from its own clock, so keep your servers synced with NTP. You don’t need to sign a 202.
Notes:
  • Use the raw request body for the body hash. Parsing and re-serializing JSON changes the bytes and breaks verification.
  • Percent-encode query keys and values the way rfc3986 does. Plain encodeURIComponent leaves ! ' ( ) * unencoded and breaks verification for IDs containing them.
  • Compare signatures with a constant-time function like crypto.timingSafeEqual.
  • For extra replay protection, cache each X-Mesh-Nonce for 60 seconds and reject duplicates.

Rotate your secret

Rotation is self-service in the Mesh dashboard. The signing secret is write-only: you can enter a new value at any time, and Mesh stores it encrypted and never shows it again.
  • Planned rotation: as soon as you save a new secret, Mesh signs every request with it. Mesh keeps accepting response signatures made with either the old or new secret for an overlap window (default 24 hours), then retires the old one. So update your endpoints before you save the new secret: verify requests against both the old and new secret, and keep signing responses with the old one. After you save, switch response signing to the new secret and drop the old one before the window ends.
  • Suspected compromise: save a new secret, then disable the previous secret on the JIT Config page to retire it immediately instead of waiting for the overlap window. Requests signed with the retired secret fail into the standard error path, with no risk of lost funds.
Keep your shared secret confidential. Store it in a secrets manager, never in client-side code or source control.
Once your JIT config is saved, omit address from any toAddresses entry you want Mesh to fetch on demand. Entries that include address work exactly as they do today.
Want help building your array? Use the toAddress generator to construct your toAddresses quickly.

Performance and reliability requirements

Handle errors

While generation is pending, the user sees a “Generating your deposit address” screen and can go back or exit at any time. If the attempt fails or the deadline passes, Mesh stops and returns the user to the previous screen, where they can choose the same or a different token and network. Mesh polls your status endpoint while generation is pending, but never calls initiate again on its own after a failure.

Allowlist Mesh’s IP addresses

Before production go-live, Mesh provides the fixed outbound IP address or range for the servers that call your JIT endpoints. You can allowlist it at your network layer to restrict the endpoints to Mesh traffic.
Allowlist a CIDR range, not a single IP. Mesh may add outbound IPs as it scales, and a range avoids an allowlist change each time. Mesh confirms the exact range in writing before go-live.

Test your implementation

Sandbox doesn’t call your JIT endpoints. For address-less entries, sandbox fills in a placeholder address instead, and it drops address-less entries on networks with no placeholder, including all memo-chain networks. Use sandbox to check that your Link Token requests with address-less entries work end to end. Test your endpoints in production behind a gate. Mesh recommends testing the full JIT flow end to end in production, with JIT entries gated to a group of your own test users and Mesh test users, before rolling out to everyone. If you can’t gate by user, Mesh can configure additional API keys for testing; ask your Mesh representative. What to test:
  • Async path: Launch a session with a toAddresses entry that has no address, select it in Link, and confirm Mesh receives 202, polls, and shows the address once ready.
  • Inline path: Select a pair whose address already exists and confirm your initiate endpoint returns 200 with the ready payload and Link shows it immediately.
  • Idempotency: Select the same pair twice (or close and reopen Link) and confirm the same address and tag come back every time.
  • Memo chain: Select XRP or XLM and confirm the ready response includes addressTag.
  • Signatures: Send a request with a bad signature or a timestamp over 60 seconds old and confirm your endpoint rejects it.
  • Failure path: Return status: failed and confirm Link returns the user to token/network selection.

What’s next

Next up: Transfer status webhooks, track every deposit, including those sent to JIT addresses, as it moves from pending to succeeded.
AI coding reference: a compact summary of this page’s APIs, parameters, and patterns for use by AI coding assistants (following the llms.txt standard). Human readers can safely ignore this.llms.txt — Just-in-Time Address FetchingJIT lets a client omit address from transferOptions.toAddresses[] entries in the Link Token request. When the user selects that token/network, Mesh calls two client-hosted HTTPS endpoints to get the address on demand (initiate-then-poll). Mesh must first enable the JIT Config page for the client; JIT is live once the initiate URL, status URL, and signing secret are saved there. Production only. Recommended: pass the full array of supported pairs; include address for any pre-generated addresses and for the most common ecosystems (Bitcoin, EVM, Solana, XRPL); use JIT for the long tail.Endpoint 1 — Initiate: POST {your_initiate_url}. Body: userId | networkId (Mesh network UUID) | symbol | transactionId (your Link Token transferOptions.transactionId, empty string if none; optional to validate; never reject before 1 hour old). Respond immediately: 202 (generation started; no body or signature needed) | 200 with the ready payload (already available; Mesh skips polling) | 200 with { "status": "failed", reason } (can’t start; attempt ends). Any other status code = failed generation.Endpoint 2 — Status: GET {your_status_url}?userId=&networkId=&symbol=&transactionId=. Always 200 with { "status": "pending" }, { "status": "ready", userId, networkId, symbol, address, addressTag? }, or { "status": "failed", reason }. Mesh polls every 2–3s until ready/failed or the overall deadline set on the JIT Config page (default about 3 min). A non-2xx, a call over 5s, or a connection failure on any single poll ends the attempt.Ready payload rules: echo userId, networkId, symbol exactly (mismatch is rejected) | address required and must pass network format validation | addressTag required for memo/tag chains (XRP, XLM, Hedera, EOS, WAX, Casper, Mina, Injective, and Cosmos-based networks such as Cosmos Hub, Osmosis, dYdX, Celestia, Crypto.org, Kava), omit otherwise.Idempotency (critical, funds-loss risk): for a given userId + networkId + symbol, always return the same address and tag. Repeat initiates must not mint a new address; status must never trigger generation. Persist on first initiate. Mesh keeps the address only within one Link session; a new session re-initiates. Pass already-generated addresses in the Link Token.Inbound auth (both endpoints): headers X-Mesh-Timestamp (unix seconds) | X-Mesh-Nonce (UUID) | X-Mesh-Signature. Signature input = jit-request:v1\n{METHOD}\n{path}\n{sorted-query-string}\n{sha256-of-body}\n{X-Mesh-Timestamp}\n{X-Mesh-Nonce}; compare hex(HMAC-SHA256(shared_secret, input)). Reject (non-200) on mismatch or timestamp skew over 60s. Canonicalization: method uppercase; path verbatim from the registered URL; query pairs sorted ordinally by decoded key then value, then RFC 3986 percent-encoded; body hash lowercase hex SHA-256 (empty string for GET); timestamp in Unix seconds.Outbound auth (every response): headers X-Mesh-Response-Timestamp (unix seconds) and X-Mesh-Signature = hex(HMAC-SHA256(shared_secret, "jit-response:v1\n" + sha256(response_body) + "\n" + X-Mesh-Response-Timestamp)). Unsigned or invalid 200 responses are rejected, as are response timestamps over 60s off. A 202 needs no body or signature.Secret rotation: self-service in the Mesh dashboard; write-only field. Mesh signs requests with the new secret immediately and accepts old-or-new response signatures for 24 hours (default), so accept the new secret on your side before saving it. For a compromise, save a new secret, then disable the previous one on the JIT Config page to retire it immediately.Performance: p99 ≤ 2s per call; 5s hard timeout per call; 99.9%+ uptime recommended; alert on failures.Errors: any failed call, failed status, initiate code other than 200/202, or non-2xx poll ends the attempt and returns the user to token/network selection. Mesh polls while pending but never re-initiates after a failure.Endpoint registration: register initiate and status URLs, the signing secret, and the overall generation deadline in the Mesh dashboard at dashboard.meshconnect.com/company/jitConfig.Network access: Mesh provides a fixed outbound IP/CIDR range before go-live; allowlist the range.Testing: sandbox never calls JIT endpoints (placeholder addresses; memo-chain address-less entries dropped). Test end to end in production, gated to client and Mesh test users, or with additional API keys Mesh configures.Link Token: POST /api/v1/linktoken with transferOptions.transferType: "deposit" and toAddresses[] entries of networkId + symbol (+ address, + addressTag for memo chains). Entries without address use JIT. Omitting address without a saved JIT config returns 400. JIT works for deposit only. Get networkIds from GET /api/v1/transfers/managed/networks (stable; cache them).For the full consolidated reference across all guides, see docs.meshconnect.com/llms-full.txt