<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Coldtill Blog]]></title><description><![CDATA[Coldtill Blog]]></description><link>https://coldtill.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Mon, 02 Nov 2026 06:46:10 GMT</lastBuildDate><atom:link href="https://coldtill.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Short, over, late: handling the crypto payments that don't match the invoice]]></title><description><![CDATA[With a card, the amount is never the customer's problem. They tap, the processor charges exactly what the invoice says, and the edge cases are declines and…]]></description><link>https://coldtill.hashnode.dev/short-over-late-handling-the-crypto-payments-that-don-t-match-the-invoice</link><guid isPermaLink="true">https://coldtill.hashnode.dev/short-over-late-handling-the-crypto-payments-that-don-t-match-the-invoice</guid><category><![CDATA[payments]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[Cryptocurrency]]></category><category><![CDATA[backend]]></category><category><![CDATA[ecommerce]]></category><dc:creator><![CDATA[Coldtill]]></dc:creator><pubDate>Sun, 01 Nov 2026 10:22:57 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ad1ef5439ff2b6b231a168d/13bd383e-e573-476c-8c34-e045dcc0d8e4.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>With a card, the amount is never the customer's problem. They tap, the processor charges exactly what the invoice says, and the edge cases are declines and chargebacks. A crypto payment is a transfer the customer makes by hand, from a wallet or an exchange you know nothing about. So the amount that arrives is whatever they typed, sent whenever they got round to it.</p>
<p>Most of the time it matches. When it doesn't, you need answers to three questions that card checkouts never asked: what counts as paid, what happens to money that arrives after the deadline, and how do you send money back?</p>
<p>I'll go through how we answer them in <a href="https://coldtill.com">Coldtill</a>, the self-hosted gateway we build, and the bits that are just good practice whatever you use.</p>
<h2>"Paid" needs a tolerance</h2>
<p>The naive rule is <code>received &gt;= owed</code>. It's wrong in both directions. A customer paying from an exchange might lose a cent to rounding and get stuck with an unpaid order over nothing. And a customer who sends 20% extra shouldn't be treated the same as one who sent the right amount, because you may owe them something.</p>
<p>Coldtill reports a payment as one of six states:</p>
<table>
<thead>
<tr>
<th>State</th>
<th>Meaning</th>
<th>Final?</th>
</tr>
</thead>
<tbody><tr>
<td><code>OPEN</code></td>
<td>Nothing confirmed yet</td>
<td>no</td>
</tr>
<tr>
<td><code>PARTIALLY_FILLED</code></td>
<td>Confirmed amount is below 99% of the amount owed</td>
<td>no</td>
</tr>
<tr>
<td><code>FILLED</code></td>
<td>Confirmed amount is within 1% of the amount owed</td>
<td>yes</td>
</tr>
<tr>
<td><code>OVER_FILLED</code></td>
<td>Confirmed amount is more than 1% above the amount owed</td>
<td>yes</td>
</tr>
<tr>
<td><code>CANCELLED</code></td>
<td>Expired before being filled</td>
<td>yes</td>
</tr>
<tr>
<td><code>REPLACED</code></td>
<td>Superseded by a newer payment for the same customer</td>
<td>yes</td>
</tr>
</tbody></table>
<p>The 1% band is the useful bit. It absorbs rounding without you writing any, and it splits "paid" into two outcomes you can treat differently. Ship on <code>FILLED</code> or <code>OVER_FILLED</code>, nothing else.</p>
<p>If you're building your own gateway, I'd copy the idea even if you pick a different number. Put the tolerance in the payment server, not in every shop that talks to it, so there's one definition of paid.</p>
<h2>Short payments: keep the door open</h2>
<p>An underpayment isn't a failure. Usually the customer forgot a network fee, or paid from an exchange that took its withdrawal fee out of the amount. The right response is to ask for the rest, not to cancel.</p>
<p>That's what <code>PARTIALLY_FILLED</code> does. It isn't final, the checkout shows the customer what's still owed, and a second transfer to the same deposit address tops it up. Each customer gets a permanent deposit address per network family (the same <code>customerID</code> always gets the same addresses), so "send the rest to the same place" is an instruction customers can actually follow.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6ad1ef5439ff2b6b231a168d/04948807-eea9-4b88-bd75-5c543888fea5.png" alt="Coldtill checkout page showing the exact amount and a QR code" style="display:block;margin:0 auto" />

<p><em>Checkout with the exact amount and a QR code. After a short payment, the same page asks for what's still owed.</em></p>
<p>On the shop side, the only job is to not ship. Put the order on hold, maybe email the customer after a while, and wait for a final event. If the payment expires while still short, it becomes <code>CANCELLED</code> and the money that did arrive is still yours to deal with, which brings us to refunds.</p>
<h2>Over-payments: ship first, decide later</h2>
<p><code>OVER_FILLED</code> is final and counts as paid. The webhook carries <code>filled_amount_in_usd</code> next to the invoice <code>amount</code>, so the difference is right there:</p>
<pre><code class="language-ts">if (ev.data.status === 'OVER_FILLED') {
  const extra = Number(ev.data.filled_amount_in_usd) - Number(ev.data.amount)
  await markOrderPaid(ev.data.invoice_id, ev.data.reference_id)
  await flagForRefundReview(ev.data.invoice_id, extra) // a human decides
}
</code></pre>
<p>Whether you refund 30 cents is a policy question. Gas on some networks costs more than that. I'd set a threshold below which you just note it, and above which someone sends it back. Either way, ship the order. Holding a paid order because the customer was generous is a strange way to treat them.</p>
<p>(<code>Number()</code> is fine for a log line. If you're doing accounting with these values, they arrive as decimal strings for a reason; keep them as decimals.)</p>
<h2>Late payments: closed means closed</h2>
<p>The awkward one. A payment expires (24 hours by default, or whatever you set with <code>expire</code> when creating it), becomes <code>CANCELLED</code>, and <em>then</em> the money arrives. Maybe the customer's transaction sat in a mempool, maybe they came back to an old tab.</p>
<p>Coldtill doesn't reopen the payment. It sends a progress event with the new <code>filled_amount</code>, and the payment stays closed. I think that's right. Reopening automatically means an order you already cancelled, and maybe restocked, flips back to paid without anyone deciding it should.</p>
<p>So your webhook handler needs a branch for "event on a payment I've already closed":</p>
<pre><code class="language-ts">const order = await findOrder(ev.data.invoice_id)
if (order.reference_id !== ev.data.reference_id) return // an old payment, not this order
if (order.status === 'cancelled' &amp;&amp; Number(ev.data.filled_amount) &gt; 0) {
  await notifyStaff(`Late payment on ${order.id}: ${ev.data.filled_amount_in_usd} USD`)
  return
}
</code></pre>
<p>Then a person picks: fulfil it anyway, or refund. Both are reasonable. Silently doing nothing isn't, because the customer has paid and is waiting.</p>
<p>The <code>reference_id</code> check matters here. A customer can only have one open payment, and starting a new checkout marks the old one <code>REPLACED</code>. Late events from that old payment must never touch the new order.</p>
<h2>Refunds are payouts, and payouts are deliberate</h2>
<p>Crypto has no "refund" button that reverses a charge. You send a new transaction. In Coldtill that's a payout from the project's hot wallet, and it comes with a few constraints you should design around rather than discover:</p>
<p><strong>Which chains.</strong> Payouts work on the EVM networks and Solana. Bitcoin deposits go straight to your own wallet, derived from your xpub, so there's no Coldtill hot wallet to pay from; Bitcoin refunds come out of your own wallet. Tron payouts aren't supported yet either.</p>
<p><strong>Where to.</strong> Through the API, payouts only go to addresses in the project's address book, and entries can only be added in the dashboard, with two-factor. So an API key, even a leaked one, can't send money to an arbitrary address. In practice this means customer refunds are a dashboard job: <strong>Payouts â†’ New payout</strong> accepts any valid address, still within your limits. API payouts suit regular destinations like suppliers or your exchange account.</p>
<p><strong>How much without asking.</strong> Each project has a minimum payout, an auto-approve amount, and hourly and daily limits. Anything over them waits for an admin, a fresh two-factor code and a code sent by email. Annoying when you're in a hurry. Very good when it's someone else in a hurry.</p>
<p><strong>Exactly once.</strong> API payouts take an <code>Idempotency-Key</code>. Use something derived from the order, like <code>refund-INV-1001</code>, and a retry after a timeout returns the original payout instead of paying twice. The same key with different parameters is refused with <code>409</code>.</p>
<pre><code class="language-bash">curl -X POST https://pay.example.com/api/v1/withdrawal/merchant \
  -H "API-Key: $COLDTILL_API_KEY" -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: refund-INV-1001' \
  -d '{"blockchainCode":"BASE","currencyCode":"USDC","amount":"12.50","addressBookId":"..."}'
</code></pre>
<p><strong>When it's unclear.</strong> If Coldtill can't tell whether a payout transaction landed, it marks it <code>needs-review</code> instead of guessing. Don't retry it. Check the hot wallet in a block explorer, then resolve it in the dashboard. A payout wrongly marked failed is how people pay twice.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6ad1ef5439ff2b6b231a168d/2aefa0da-75bb-453b-89bc-4861feef4cbd.png" alt="Coldtill dashboard Stats page" style="display:block;margin:0 auto" />

<p><em>The Stats page. The completion rate is a quick way to see whether short and expired payments are a real problem for your shop or a rare one.</em></p>
<h2>Ask for the refund address</h2>
<p>One general rule, not specific to any gateway: don't refund to the address the payment came from. If the customer paid from an exchange, that "from" address belongs to the exchange, and money sent there may never reach them. Ask the customer for an address on the same network and asset, and keep the request in your support history.</p>
<p>Auto-convert payments are the exception that proves it. When a customer pays with a coin you don't take directly, Coldtill's checkout asks for their refund address up front, on that coin's chain. If the swap fails or they send too little, the swap provider refunds that address and your shop isn't involved.</p>
<h2>The short version</h2>
<ul>
<li>Put the "what counts as paid" tolerance in one place.</li>
<li>Treat short payments as unfinished, not failed.</li>
<li>Ship over-payments; refund the extra by policy.</li>
<li>Never let a late or replaced payment change an order on its own.</li>
<li>Make refunds deliberate: known address, limits, an idempotency key, and no retries on "unknown".</li>
</ul>
<p>If you want to look at the gateway behind these examples, the Free edition's install script and release files are in <a href="https://github.com/Coldtill/self-hosted-crypto-payment-gateway">the GitHub repo</a>, and the payout rules are written up in full in the <a href="https://coldtill.com/docs/payouts/">payouts docs</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Assume the payment server gets hacked: hardening a self-hosted crypto gateway]]></title><description><![CDATA[When you take card payments through a hosted provider, server security is mostly someone else's problem. Your shop never sees a card number, and the money…]]></description><link>https://coldtill.hashnode.dev/assume-the-payment-server-gets-hacked-hardening-a-self-hosted-crypto-gateway</link><guid isPermaLink="true">https://coldtill.hashnode.dev/assume-the-payment-server-gets-hacked-hardening-a-self-hosted-crypto-gateway</guid><category><![CDATA[Security]]></category><category><![CDATA[Devops]]></category><category><![CDATA[#selfhosted]]></category><category><![CDATA[Blockchain]]></category><dc:creator><![CDATA[Coldtill]]></dc:creator><pubDate>Sat, 31 Oct 2026 10:11:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ad1ef5439ff2b6b231a168d/aba82922-ff89-4954-80e8-c0d972eb8a76.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When you take card payments through a hosted provider, server security is mostly someone else's problem. Your shop never sees a card number, and the money sits with the provider until it pays you. Run your own crypto payment server and that changes. The machine that creates invoices is now part of the path your money takes, and it's on a public IP.</p>
<p>I find the most useful way to think about hardening it isn't a checklist. It's a question: <strong>if someone gets root on this box tomorrow, what do they walk away with?</strong> Everything else follows from the answer.</p>
<p>I'll use <a href="https://coldtill.com">Coldtill</a>, the self-hosted gateway we build, as the worked example, because I know exactly where its lines are drawn. The general approach applies to any gateway you run yourself.</p>
<h2>Start with the blast radius</h2>
<p>Here's how the Coldtill security docs describe it:</p>
<table>
<thead>
<tr>
<th>Funds</th>
<th>Who can move them</th>
</tr>
</thead>
<tbody><tr>
<td>Customer deposits (EVM chains, Solana)</td>
<td>Anyone can trigger a sweep, but it can only go to your cold wallet</td>
</tr>
<tr>
<td>Bitcoin deposits</td>
<td>Only you: they land in your own wallet, derived from your xpub</td>
</tr>
<tr>
<td>Your cold wallets</td>
<td>Only you. The server never sees those keys</td>
</tr>
<tr>
<td>Hot wallets</td>
<td>The sweeper and payout processes, after decrypting the key with your passphrase</td>
</tr>
</tbody></table>
<p>So a complete server compromise can, at worst, do two things: spend whatever is in the hot wallets, and tamper with payment records, for example marking an unpaid order as paid. It can't redirect deposits, because on Ethereum-style chains, Tron and Solana each deposit address is a contract that can only send to your cold wallet, and Bitcoin addresses come from your own wallet's public key.</p>
<p>That's a much smaller target than "all the money". But notice what's left. Both remaining risks are things you control with configuration, which is what the rest of this post is about.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6ad1ef5439ff2b6b231a168d/dc025156-38e5-4f25-98d9-030cdaf85eba.png" alt="" style="display:block;margin:0 auto" />

<p><em>The Stats page. "Waiting to sweep" shows funds still sitting on deposit addresses. They can only ever move to the cold wallet, which is why they aren't the thing you're defending.</em></p>
<h2>1. Keep the hot wallet boring</h2>
<p>The hot wallet pays gas for sweeps and sends approved payouts and refunds. It's the one wallet an attacker with root could drain, so the rule is simple: it should hold about a week of gas and payouts, no more. If losing its balance would hurt, it's too big.</p>
<p>Around it, set the payout controls tight:</p>
<ul>
<li>A <strong>low auto-approve amount</strong>, plus hourly and daily limits. Anything over the limits waits for an admin, a fresh two-factor code and an emailed one-time code.</li>
<li>API payouts can only go to <strong>address-book entries</strong>, and adding to the address book needs two-factor.</li>
<li>Give shop plugins <strong>payments-only API keys</strong>. A key that can create invoices but can't send money is a much less interesting thing to steal. Keep full-access keys for a backend that genuinely sends payouts.</li>
</ul>
<p>Internally, Coldtill splits keys by job. Hot wallet keys are encrypted with <a href="https://age-encryption.org">age</a>, and only the sweeper and payout containers get the identity and passphrase. The web-facing API process can encrypt an imported key but never decrypt one. That's the reason the API, the piece exposed to the internet, holds no keys at all.</p>
<p>By default the passphrase is stored on disk so sweeps survive a restart. If you want more, you can run without a stored passphrase and type it on every start. That's a real trade-off: an unattended reboot means sweeps wait for you.</p>
<h2>2. Make the cold wallet properly cold</h2>
<p>Everything swept ends up in the cold wallet, and the server only ever needs its address. That means there's no excuse for it to be a browser wallet. Use a hardware wallet, or a multisig (Safe on EVM chains, Squads on Solana).</p>
<p>Then do the step people skip: when the dashboard derives the first deposit address, check it against your own wallet before sharing a payment link. Do it again after any change to deposit wallets. It takes two minutes and it's the one check that proves the money is going where you think.</p>
<h2>3. Get the dashboard off the internet</h2>
<p>The checkout page and the merchant API have to be public. The login page doesn't. The dashboard is where payouts get approved, so the fewer people who can even load it, the better. The self-hosting guide offers three ways, roughly in order of effort:</p>
<ol>
<li><strong>A separate checkout domain.</strong> Give the project a checkout domain like <code>pay.example.com</code>. On that name Coldtill serves only the checkout, the API and static files; the login page and <code>/api/dash</code> return 404. Keep the dashboard on a name you don't publish.</li>
<li><strong>Cloudflare in front</strong>, with Cloudflare Access (or a WAF rule) protecting the dashboard paths while leaving <code>/pay/</code>, <code>/api/checkout/</code>, <code>/api/v1/</code> and the static paths open.</li>
<li><strong>Tailscale</strong>, so the dashboard is only reachable on your private network, with the public hostname used only for checkout and the API.</li>
</ol>
<p>The strongest setup combines Tailscale for signing in with a Cloudflare rule that blocks the dashboard on every public name. On top of that, two-factor should be on for every account, and people should get the lowest role that works: <code>developer</code> for integration work, <code>read_only</code> for support.</p>
<h2>4. Docker will quietly ignore your firewall</h2>
<p>This one catches experienced people. You enable <code>ufw</code>, allow only Cloudflare's ranges on 443, and feel good about it. But ports published by Docker Compose are opened in iptables ahead of ufw's rules, so anyone can still reach Caddy directly.</p>
<p>Two fixes. The better one is your hosting provider's firewall (Hetzner Cloud Firewall, DigitalOcean Cloud Firewall, AWS security groups), which filters before traffic reaches the machine. The other is the <code>DOCKER-USER</code> chain, which Docker consults before its own rules:</p>
<pre><code class="language-bash">for r in $(curl -fsS https://www.cloudflare.com/ips-v4); do
  sudo iptables -I DOCKER-USER -i eth0 -s "$r" -p tcp -m multiport --dports 80,443 -j RETURN
done
sudo iptables -A DOCKER-USER -i eth0 -p tcp -m multiport --dports 80,443 -j DROP
sudo apt install iptables-persistent &amp;&amp; sudo netfilter-persistent save
</code></pre>
<p>Swap <code>eth0</code> for your public interface, and repeat with <code>ip6tables</code>. Then test it from another machine instead of trusting it:</p>
<pre><code class="language-bash">curl -sk --resolve pay.example.com:443:&lt;server-ip&gt; https://pay.example.com/healthz
</code></pre>
<p>If you're behind Cloudflare, that should time out.</p>
<p>The usual basics still apply: SSH keys only, <code>PasswordAuthentication no</code>, unattended security upgrades, and a machine of its own. Don't run the payment server on the same box as your shop. A WordPress plugin bug shouldn't be one hop from your hot wallet.</p>
<h2>5. Don't trust a single node</h2>
<p>This is the risk that's easy to miss because it isn't about keys. The gateway trusts RPC nodes to report deposits honestly. A malicious or broken node could report a deposit that never happened, and the order gets marked paid.</p>
<p>Mitigations:</p>
<ul>
<li>Use nodes you run or pay for, not public endpoints (those are rate limited and meant for testing).</li>
<li>Add at least two per network, from different providers, so failover works.</li>
<li>Large orders: look the deposit up on a block explorer before you ship.</li>
</ul>
<p>Coldtill helps a bit here: Bitcoin deposits are only treated as orphaned when every node agrees the transaction is gone, EVM chain IDs are signed into every transaction, and the Solana genesis hash is pinned, so a node on the wrong network can't trick the signers. Adding a node also needs a fresh two-factor code. But diversity of providers is still on you.</p>
<h2>6. Verify what you install</h2>
<p>The install script downloads the release and checks its SHA-256 before installing. If you install by hand instead, every release in <a href="https://github.com/Coldtill/self-hosted-crypto-payment-gateway">the GitHub repo</a> ships a <code>SHA256SUMS</code> file:</p>
<pre><code class="language-bash">sha256sum -c SHA256SUMS --ignore-missing
</code></pre>
<p>Updates are checked once a day and shown as a banner to admins. Security advisories show up as a red banner, and critical and high ones are emailed. Re-running the installer upgrades and backs up the database first.</p>
<h2>7. Backups you've actually restored</h2>
<p>Two things need backing up, and they're different:</p>
<ul>
<li><strong>The database.</strong> Dump it nightly and copy the dumps off the server. A backup on the same disk doesn't survive losing the disk. The docs use <a href="https://restic.net">restic</a> to an S3-compatible bucket as the example.</li>
<li><code>deploy/.env</code> <strong>and</strong> <code>deploy/secrets/</code><strong>.</strong> Keep these in encrypted offline storage. Without <code>.env</code>, the two-factor and webhook secrets stored in the database can't be read, so a database backup alone gets you less than you'd hope.</li>
</ul>
<p>Then test a restore on a spare server at least once. An untested backup is mostly a feeling.</p>
<h2>8. Watch the right things</h2>
<p>Not much, but the right things:</p>
<ul>
<li>An uptime monitor on <code>/healthz</code>.</li>
<li>Alerts on <code>"level":"ERROR"</code> in the logs.</li>
<li><strong>Hot wallet balances.</strong> When they run dry, sweeps stop. It's not a security incident, but it looks like one at 2am.</li>
<li>A webhook subscription for <code>payout.needs-review</code>. That status means Coldtill couldn't tell whether a payout transaction landed, and it deliberately doesn't guess. Don't retry the payout until someone has checked the hot wallet's history.</li>
</ul>
<h2>Compared with the hosted route</h2>
<p>None of this exists if you use a hosted processor, and that's a fair point in its favour. You trade this list for an application process, a percentage of every sale and an account that can be frozen. Self-hosting doesn't remove risk; it moves it to places you can see and control.</p>
<p>What I like about starting from "assume it's breached" is that it tells you where effort pays off. With deposits locked to the cold wallet, almost all of the remaining exposure is the hot wallet float, the dashboard and the nodes. Keep the float small, the dashboard private and the nodes plural, and a compromise becomes an expensive inconvenience rather than a disaster.</p>
<p>The full threat model and security model are published at <a href="https://coldtill.com/docs/security/">coldtill.com/docs/security</a>, including the risks that remain.</p>
]]></content:encoded></item><item><title><![CDATA[Which chain should your crypto checkout settle on?]]></title><description><![CDATA[A merchant's guide to networks, fees, finality and keeping your books in dollars when customers pay in crypto.]]></description><link>https://coldtill.hashnode.dev/which-chain-should-your-crypto-checkout-settle-on</link><guid isPermaLink="true">https://coldtill.hashnode.dev/which-chain-should-your-crypto-checkout-settle-on</guid><category><![CDATA[Blockchain]]></category><category><![CDATA[Cryptocurrency]]></category><category><![CDATA[payments]]></category><category><![CDATA[ecommerce]]></category><dc:creator><![CDATA[Coldtill]]></dc:creator><pubDate>Thu, 29 Oct 2026 10:09:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ad1ef5439ff2b6b231a168d/c2d1fd28-075c-4e5c-941d-37f20677e3ca.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>"Which coins should we accept?" is usually the first question a shop asks when it adds crypto. I think it's the wrong first question. The better one is: <strong>where do you want the money to end up, and in what?</strong></p>
<p>Customers care about paying with whatever is already in their wallet. You care about what lands in yours, what it cost to get there, how long until it's final and how your accountant will feel about it. Those are separate decisions, and treating them separately makes the whole thing much simpler.</p>
<p>I'll walk through how I think about it, using the networks <a href="https://coldtill.com">Coldtill</a> supports directly (Ethereum, Base, Polygon, BNB Chain, Tron, Solana and Bitcoin) as the examples, since that's the gateway we build and the trade-offs are ones I deal with every week.</p>
<h2>Separate "accept" from "settle"</h2>
<p>It helps to split the problem in two:</p>
<ul>
<li><strong>Settlement assets</strong> are what you're happy to hold. For most shops that's one or two stablecoins on one or two networks, and maybe Bitcoin if you actually want Bitcoin.</li>
<li><strong>Accepted assets</strong> are everything a customer can pay with. That list can be much longer, as long as anything outside your settlement set is converted on the way in.</li>
</ul>
<p>In Coldtill, settlement assets live on networks where you have a deposit wallet and your own nodes watching the chain. Everything else can come in through auto-convert: a swap provider, NEAR Intents 1Click by default, turns the customer's coin into USDC or USDT and delivers it to your cold wallet. That's where the "200+ coins on 35+ chains" figure comes from. You don't run nodes for any of those; you run nodes for the networks you settle on.</p>
<p>So the real decision is a short one: which one or two networks do you settle on?</p>
<h2>What to weigh for each network</h2>
<h3>Fees, for your customer and for you</h3>
<p>There are two sets of fees in a self-hosted setup, and people often only think about the first.</p>
<ol>
<li><strong>The customer's fee</strong> to send the payment. This is their cost, but a high one means abandoned checkouts, especially on small orders.</li>
<li><strong>Your fee to move the money onward.</strong> With per-customer deposit addresses, funds eventually have to be swept from those addresses to your cold wallet, and that costs gas.</li>
</ol>
<p>The second one is where design matters. Coldtill batches sweeps per network and only sweeps when it's worth it: by default when the unswept total reaches $100, when 20 addresses hold funds, or when the oldest deposit has waited 24 hours, and only if gas is a small enough share of the value. On Tron, sweeps can rent energy instead of burning TRX. Bitcoin is different: addresses are derived straight from your own wallet's public key, so payments land in your wallet and there's nothing to sweep at all.</p>
<p>As a rule of thumb, if your average order is small, a low-fee network for both the customer and the sweep matters more than anything else on this list. Ethereum mainnet is the most widely held but usually the priciest to transact on; Base, Polygon, BNB Chain, Tron and Solana are where small stablecoin payments tend to make more sense. Check current fees yourself, since they move.</p>
<h3>Finality: when is it actually yours?</h3>
<p>A transfer isn't settled the moment it's seen. Each network needs a number of confirmations before a payment counts, and reorgs, while rare on most chains, do happen. In Coldtill each network has its own configured confirmation count, block hashes are re-checked, and a deposit whose block disappears goes back to pending. For auto-converted payments on Solana, the delivery has to reach <code>finalized</code> before the order is credited.</p>
<p>What this means for a merchant is simple: faster finality means a shorter wait on the checkout page. For digital goods where the customer is staring at a spinner, that wait matters. For a physical order that ships tomorrow, it barely does.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6ad1ef5439ff2b6b231a168d/29ba6f9c-edea-41a4-84bf-115a8b0c379a.png" alt="" style="display:block;margin:0 auto" />

<p><em>At checkout the customer picks a network and coin; settlement networks stay direct and the rest go through auto-convert</em></p>
<h3>Which stablecoin, and where</h3>
<p>USDC and USDT are both available on most of these networks, and customers have strong habits. Some will only ever hold USDT on Tron; others have USDC on Base from an exchange withdrawal. If you settle in only one, the other customers either pay a conversion or go elsewhere.</p>
<p>A reasonable starting point for a lot of shops is one USDC network and one USDT network, and then watching what people actually use. In Coldtill, auto-convert can settle in USDC or USDT on Ethereum, Base, Polygon, BNB Chain or Solana, so you can pick one of those as the landing spot for converted payments too.</p>
<h3>What you can do with the money afterwards</h3>
<p>This one gets missed. If you'll need to send refunds or pay suppliers in crypto, check where your tooling supports outgoing payments. In Coldtill, payouts run from a small hot wallet on the EVM networks and Solana, with an address book, hourly and daily limits and admin approval for large ones. If refunds matter to you, settling on one of those networks keeps things simple.</p>
<h2>Keeping the books in dollars</h2>
<p>Accountants don't love a ledger in 40 tokens. The easiest way to keep them happy is to make sure every payment is recorded with a dollar value at the time it was paid, and that what you hold stays in stablecoins.</p>
<p>A few things make reconciliation easier, whatever gateway you use:</p>
<ul>
<li><strong>Price the order in USD.</strong> Coldtill payments are created with <code>amountInUSD</code>, and the webhook reports <code>filled_amount_in_usd</code> alongside the raw on-chain amounts. Stablecoins count as $1; other coins are priced from CoinGecko, with Coinbase as a fallback.</li>
<li><strong>Store the on-chain detail.</strong> Each payment event includes a <code>payment_info</code> list with network, currency, amount, transaction hash and block number for every deposit. Keep it. It's your audit trail, and it lets you match deposits against your cold wallet's history.</li>
<li><strong>Record conversions separately.</strong> Auto-converted payments carry a <code>conversion</code> array with what the customer sent (for example BTC), its USD value at the time and the stablecoin delivered. That difference is the swap cost, and it's useful to see it as its own line rather than buried in revenue.</li>
<li><strong>Expect small differences.</strong> A payment within 1% of the amount owed counts as filled, and anything more than 1% above is flagged as over-filled. Decide how you book the leftovers before they pile up.</li>
<li><strong>Only count money that actually arrived.</strong> With auto-convert, Coldtill treats the swap provider's status reports as hints, and only credits an order once your own nodes see the stablecoin land in your cold wallet with full confirmations. Your books should work the same way: revenue is what reached your wallet, not what a provider said was on its way.</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/6ad1ef5439ff2b6b231a168d/a9a31090-dfc3-4eef-8745-36194dab9715.png" alt="" style="display:block;margin:0 auto" />

<p><em>The stats page splits revenue into coins kept directly and payments swapped through NEAR Intents, and shows what's still waiting to be swept</em></p>
<h2>Exchanges, memos and the customers who pay from them</h2>
<p>A surprising number of customers pay straight from an exchange account. That's fine on most direct routes, but some swap routes need a memo, which exchanges often can't add. NEAR Intents 1Click deposits need no memo, so customers can pay from an exchange; the optional THORChain fallback does need one, so those customers must pay from their own wallet. If a swap fails or the customer sends too little, the provider refunds the refund address the customer entered at checkout.</p>
<p>If a lot of your customers buy on exchanges, it's worth testing that path yourself before you launch.</p>
<h2>A sensible default</h2>
<p>If I were setting up a new shop tomorrow with no data on what customers hold, I'd do this:</p>
<ol>
<li>Settle in one cheap, widely used stablecoin network, plus a second one in the other stablecoin if your customers are split.</li>
<li>Add Bitcoin directly only if you actually want to hold Bitcoin. Otherwise let it come in through auto-convert.</li>
<li>Turn auto-convert on for everything else, with a slippage limit you're comfortable with (Coldtill allows 0.1% to 3%).</li>
<li>Look at the "how customers paid" split after a month and adjust.</li>
</ol>
<p>There's no perfect network. There's just the one that fits your order sizes, your customers and your accountant. If you want to see how these choices look in practice, there's a live demo dashboard with made-up data on <a href="https://coldtill.com">coldtill.com</a>, and the <a href="https://coldtill.com/docs/auto-convert/">auto-convert docs</a> go into the checks in more detail.</p>
]]></content:encoded></item><item><title><![CDATA[Hosted processor, BTCPay Server or a multi-chain gateway: picking a crypto checkout in 2026]]></title><description><![CDATA[Three ways to take crypto in an online shop, what each one trades away, and the questions worth asking before you commit.]]></description><link>https://coldtill.hashnode.dev/hosted-processor-btcpay-server-or-a-multi-chain-gateway-picking-a-crypto-checkout-in-2026</link><guid isPermaLink="true">https://coldtill.hashnode.dev/hosted-processor-btcpay-server-or-a-multi-chain-gateway-picking-a-crypto-checkout-in-2026</guid><category><![CDATA[Cryptocurrency]]></category><category><![CDATA[payments]]></category><category><![CDATA[self-hosted]]></category><dc:creator><![CDATA[Coldtill]]></dc:creator><pubDate>Sun, 25 Oct 2026 12:50:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ad1ef5439ff2b6b231a168d/0cfdfd72-106c-4514-9d19-e9b85a32783d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When a shop decides to accept crypto, the first instinct is to search for a plugin and install whatever has the most reviews. I've been on both sides of this, wiring checkouts into stores and now building one, and the choice matters more than it looks. Swapping payment providers later means touching your order flow, your accounting and your customers' habits.</p>
<p>There are roughly three families of options. None is wrong. They just optimise for different things.</p>
<h2>1. Hosted crypto processors</h2>
<p>This is the "Stripe for crypto" model. You create an account, pass a business review, drop in their plugin, and they host the checkout. Customers pay to addresses the processor controls, and you get paid out on their schedule, often converted to fiat.</p>
<p><strong>Where it shines:</strong> speed to launch, no servers to look after, fiat settlement if that's what your accountant wants.</p>
<p><strong>What you give up:</strong></p>
<ul>
<li>Someone has to approve you, and can change their mind later.</li>
<li>The processor holds the money between payment and payout, so your balance can be frozen under their terms.</li>
<li>There's a percentage fee on every sale for as long as you use it.</li>
<li>Customer and order data is shared with a third party.</li>
</ul>
<p>If you're testing whether customers even want to pay in crypto, a hosted processor is a perfectly reasonable place to start.</p>
<h2>2. BTCPay Server</h2>
<p>BTCPay Server is the project most people mean when they say "self-hosted crypto payments", and it earned that reputation. It's free, open source, runs on your own server and pays straight into your own wallet. For a Bitcoin-first shop, especially one that wants Lightning, it's hard to beat.</p>
<p><strong>Where it shines:</strong> Bitcoin and Lightning, a mature ecosystem, no fees, full control.</p>
<p><strong>What to think about:</strong> its core focus is Bitcoin. Other coins come through plugins, and swap plugins tend to convert altcoins <em>into</em> Bitcoin. If your customers mostly want to pay in USDC on Base or USDT on Tron, and you'd like your books to stay in dollars, you'll be working against the grain a bit.</p>
<h2>3. Self-hosted multi-chain gateways</h2>
<p>The third group runs on your own server like BTCPay, but is built around many chains and stablecoins from the start. SHKeeper, Bitcart and our own project, <a href="https://coldtill.com">Coldtill</a>, sit here, and they differ a lot in the details.</p>
<p>We built Coldtill because we wanted a self-hosted crypto payment gateway that took stablecoins seriously and assumed the server would one day be attacked. It runs in Docker on a small Linux box, takes native coins plus USDC and USDT on the main networks, and with auto-convert switched on accepts 200+ coins across 35+ chains while settling in a stablecoin. Every deposit address can only send to the merchant's cold wallet, so the web-facing server holds no keys that could redirect funds.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6ad1ef5439ff2b6b231a168d/ec9b1919-87d0-4eeb-b003-204fda9dd703.png" alt="Coldtill checkout, choosing a coin" style="display:block;margin:0 auto" />

<p>The trade-off: it isn't open source. The WooCommerce plugin is MIT-licensed, but the core app is proprietary under an EULA, with a Free edition (2% fee), Standard ($499 one-time, 1% fee) and Pro ($999 one-time, no fee). If fully open source matters to you, that's a fair reason to pick something else.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6ad1ef5439ff2b6b231a168d/97e32d06-631a-430e-a1df-956d36b6a475.png" alt="Coldtill dashboard overview" style="display:block;margin:0 auto" />

<h2>What to check before you pick one</h2>
<p>Whatever you shortlist, these are the questions I'd ask:</p>
<p><strong>Who holds the money, and when?</strong> Follow a payment from the customer's wallet to yours. If there's any point where a third party, or a hot wallet on an internet-facing server, holds the funds, know how much and for how long.</p>
<p><strong>What happens if the server is compromised?</strong> For self-hosted options, this is the big one. Can an attacker with root on the box change where deposits go? Are signing keys on the same machine as the public API?</p>
<p><strong>Which coins do your customers actually use?</strong> If it's mostly stablecoins on cheap networks, prioritise those. If it's Bitcoin, prioritise Bitcoin.</p>
<p><strong>What do you end up holding?</strong> Accepting 50 tokens is less appealing if you then hold 50 tokens. Check whether conversion is built in and who runs it.</p>
<p><strong>How are webhooks signed?</strong> Look for HMAC signatures with a timestamp for replay protection, documented retries and idempotent event IDs. Sloppy webhooks are the most common way crypto orders get marked paid when they weren't.</p>
<p><strong>What's the real cost over two years?</strong> Add up the percentage fees at your expected volume and compare them with one-time licences and server costs.</p>
<p><strong>Can it be switched off?</strong> Is there a licence server, a kill switch or an account that can be closed? Does it keep working if the vendor's site goes down?</p>
<p><strong>Who maintains it?</strong> Look at release history, security advisories and how the project handles reported vulnerabilities.</p>
<h2>My honest take</h2>
<p>If you want zero operations, use a hosted processor. If you're Bitcoin-first and want fully open source, use BTCPay Server. If you want to run it yourself, take a wide range of coins and settle in stablecoins without any custody in the middle, look at the multi-chain gateways, ours included. There's a full comparison and a live demo dashboard on <a href="https://coldtill.com">coldtill.com</a> if you want to see how it looks before installing anything.</p>
]]></content:encoded></item></channel></rss>