← halfin journalApr 02, 2026 · 8 min read
Playbooks

Crypto for prop firms: funding challenges in, paying winners out

How to take stablecoin deposits for challenge fees with a fiat-anchored, rate-locked invoice, and how to release trader profits through approved payouts without leaking treasury control.

SB
S. BrandtSolutions
playbooks · cover

A prop firm is two money flows wearing one brand. Money comes in priced in dollars. Money goes out as a promise you'd better keep. Crypto changes the rails under both, and almost nothing else.

A proprietary trading firm sells evaluations. A trader pays a fee, passes a challenge, gets a funded account, and earns a profit split. The whole business runs on two transfers: the challenge fee coming in, and the profit payout going out. Card processors hate this model, treat it as high-risk, and reach for chargebacks the moment a trader fails an evaluation and regrets the spend. Crypto removes the chargeback, but only if you set up both flows deliberately. Here is how we set them up.

The deposit: price in dollars, settle in stablecoin

The mistake we see first is firms quoting the challenge fee in crypto. "Send 0.0043 BTC for the $250 Phase 1." Don't. Your challenge tiers are priced in dollars, your P&L is in dollars, and the trader is mentally spending dollars. Quote the fee that way.

That is exactly what a fiat-anchored invoice does. You create an invoice with amount_fiat: "250.00" and fiat_currency: "USD", and halfin locks an asset amount at the rate that's live when the invoice activates. The trader sees "$250.00" and "pay this in USDT," the rate holds for the life of the invoice, and you receive a known dollar value of stablecoin — not a position whose value drifts between the click and the confirmation.

For challenge fees, stablecoins are the obvious default. USDT and USDC don't move while the deposit confirms, so a passed Phase 1 and a failed one settle for the same amount you quoted. We cover the USDT-vs-USDC split for exactly this use case in a separate post; the short version is that both are first-class, and you expose both rather than betting the firm on one.

A few details that matter specifically for evaluation sales:

  • Set an expiry that matches your funnel. A trader who opens a challenge checkout and wanders off should get a clean expired invoice, not a deposit address that's live forever. When it lapses, your front end fires the invoice.expired webhook and you re-offer the tier.
  • Handle the underpay. Network fees and wallet rounding mean a trader occasionally sends a few cents short. That fires invoice.underpaid, not invoice.paid. Decide the policy once — credit it, top-up prompt, or hold — and wire it to that event instead of treating every non-paid outcome as a failure.
  • Use a redirect back into your platform. Set redirect_url so a paid trader lands back on their dashboard with the challenge already provisioning, instead of staring at a checkout page wondering whether it worked.

The full deposit pattern, including the multi-tier challenge catalog and the chargeback math, lives on the prop-firm deposits page. This post is the operator's-eye view of why each piece is there.

Provision the account off the webhook, not the redirect

The redirect is a convenience for the human. It is not proof of payment. A trader can close the tab before the redirect fires, and a clever one can hit the redirect URL by hand.

Provision the funded account when you receive and verify the invoice.paid webhook — never on the front-end return. The event arrives signed; verify the HMAC over the raw request bytes before you act on it, because re-serializing the JSON to "clean it up" will change a byte and break the signature. Treat the delivery as at-least-once: store the event ID, and if the same invoice.paid lands twice, no-op the second one rather than handing out two funded accounts for one fee.

The redirect tells the trader the payment worked. The webhook tells you. Build the account on the second signal, every time.

For larger funded tiers you'll also want to react to invoice.confirming — the deposit is seen but not yet final — to show the trader a "we've got it, finalizing" state instead of a dead checkout. And keep a handler for invoice.overpaid so an over-send becomes a balance credit you can reconcile, not a support ticket.

The payout: profit splits without handing over the keys

The out-flow is where prop firms get nervous, and they're right to. Paying a profit split means moving real money to someone you've never met, on a schedule, at volume. The two failure modes are paying the wrong person and paying the wrong amount — both are unrecoverable on-chain.

halfin payouts are built so the person who composes a payout run is not necessarily the person who releases it. You build the run — single trader or a fan-out of hundreds — by posting each payout to POST /api/v1/payouts. There is no batch endpoint to reason about; a mass run is just many payouts, each idempotent on its own idempotency_key. They land in pending-approval. Nothing leaves the treasury until someone with the right permission releases them from the dashboard.

That gap is the whole point. It's where a human (or a second system) checks the run against the firm's books before any coin moves:

  • The idempotency_key is the seatbelt against the classic double-pay. If your payout job retries after a network blip, the same key collapses to one payout instead of two. Derive it from something stable — the profit-cycle ID plus the trader ID — not from a fresh UUID per attempt. See idempotency key for how to choose one that survives retries.
  • Pay winners on the rail they cash out on. A trader withdrawing to a Tron-based exchange wallet wants USDT on Tron; one parked on an L2 wants USDC on Base. The payout's currency picks the chain. Asking "where do you withdraw?" during trader onboarding saves a re-bridge and a support thread later.
  • Reconcile on payout.completed and payout.failed, not on the API response. The POST acknowledges the instruction; the webhook reports the on-chain outcome. A payout.failed (bad address, dust below minimum) should re-open the payout in your system, not silently vanish.

If profit settlement is genuinely large or recurring, balance conversion lets the firm hold treasury in one asset and pay out in another — asset to asset, settled internally. It is not a fiat off-ramp; it doesn't turn stablecoin into a bank wire. It just means you don't have to pre-stage every payout currency by hand.

The shape of a clean prop firm integration

Strip it down and the whole thing is four moving parts:

  1. A fiat-anchored invoice per challenge tier — dollar-priced, rate-locked, stablecoin-settled, with an expiry and a redirect.
  2. A invoice.paid webhook handler that provisions the funded account once, idempotently, after verifying the signature.
  3. A payout builder that fans out profit splits into pending-approval and waits for a human release.
  4. A reconciliation loop keyed off payout.completed / payout.failed, not off the request that created them.

None of this is exotic. It's the same invoice-and-payout primitives every halfin merchant uses, arranged for a business that sells evaluations and pays out winners. The reason it works for prop firms specifically is the chargeback that never comes and the approval gate that sits between "compose a payout" and "the coins are gone."

The deposit side has its own vertical page with the challenge-catalog detail, and it sits under the broader FX and brokerage hub if you're settling for an introducing broker or a copy-trading desk on the same stack. Same primitives, different funnel.

Operating rule

Quote the fee in dollars, provision off the verified webhook, and never let one person both compose and release a payout. Get those three right and the rest is the plumbing we already built.

S. Brandt, halfin solutions team

↳ end of articlehalfin journal · Apr 02, 2026