If a listing promises a one-click halfin Shopify plugin, close the tab. We didn't build one, and the people selling you a shortcut are selling you a reconciliation bug.
Every week someone asks us for the halfin Shopify app. There isn't one. That answer disappoints people for about thirty seconds, until they understand that the thing they were asking for would have been worse than the thing they're going to build instead. This is the honest version of how crypto-on-Shopify actually works, and what you wire up to make it solid.
Why there's no plugin, and why that's fine
Shopify locks the payment step. Custom payment methods that take money inside the native checkout are gated behind Shopify Payments and a short list of approved gateways. A crypto invoice that locks a rate, waits for on-chain confirmations, and handles underpayment does not fit inside that gated step — and pretending it does is how you end up with orders marked paid before a single confirmation has landed.
So the integration moves one layer out. The buyer leaves the native checkout, pays a halfin invoice, and your store reconciles the order from a signed webhook. That's not a workaround. It's the correct shape for an asset that settles on a chain instead of on a card network.
The full step-by-step lives in the Shopify how-to guide. This post is the opinionated layer on top: the decisions that guide doesn't make for you.
The two real paths
There are exactly two ways to do this, and your engineering appetite picks one.
Path one: the hosted-checkout link. You create a hosted checkout invoice anchored to the order total, hand the buyer the URL, and let halfin host the entire payment page — QR, chain selection, rate lock, the confirming and paid states. You write almost no payment UI. This is the right default for most stores, and it's where you should start.
Path two: the API flow. You call POST /api/v1/invoices from your app, store the returned invoice ID against the Shopify order, and either redirect to the hosted page or render your own. More control, more code. Choose this when you're embedding crypto into an existing custom storefront, not a stock Shopify theme.
Both paths converge on the same truth: the order is not paid when the buyer clicks pay. It's paid when the webhook says so.
What actually gets wired up
Four pieces. None of them are optional.
- An invoice per order, anchored to fiat. Set
amount_fiatandfiat_currencyto the Shopify order total. Don't pin a crypto amount — the buyer's cart is priced in your store's currency, and you want halfin to lock the rate at checkout time. This is the deferred-and-anchored pattern doing exactly what it's for. - A
redirect_urlback to your store. After payment, the buyer should land on your order-confirmation page, not on a halfin screen wondering what just happened. - The order's Shopify ID carried through. Stash it so the webhook handler can find the order again. The invoice ID ↔ order ID mapping is the spine of the whole integration.
- A webhook endpoint that verifies before it acts. This is the part people skip, and it's the part that matters most.
The webhook is the integration
A buyer paid an invoice. Shopify has no idea. The only thing that closes that loop is halfin firing a webhook at your endpoint, and your endpoint marking the Shopify order paid.
The events you care about:
Two non-negotiables before any of that runs:
Verify the signature on the raw bytes. Every webhook ships an X-Halfin-Signature header. Compute your HMAC over the exact body you received, not the re-serialized JSON — key order and whitespace will drift the moment you parse and re-stringify, and your signature check will fail for reasons that have nothing to do with security. We wrote a whole post on verifying the signature before acting; read it before you ship.
Be idempotent. Webhooks retry. You will receive invoice.paid more than once, and a Shopify order you mark paid twice is a fulfillment you ran twice. Key your handler on the event ID, record what you've processed, and no-op on a repeat. There is no Idempotency-Key header to lean on here — idempotency on the receiving side is your job, and it's the difference between "we accept crypto" and "we accidentally shipped two orders."
invoice.activatedis not a thing. If a tutorial tells you to listen for it, that tutorial was written by something that has never seen our event list.
The edge cases that bite e-commerce specifically
Card payments fail closed: declined means no money moved. Crypto fails open — the money can arrive late, arrive short, or arrive after the buyer already gave up and refreshed. A Shopify store has to hold a few states a card store never thinks about.
- Underpaid carts. Someone sends 0.0009 BTC for a 0.001 BTC invoice because their wallet skimmed a fee off the top. The order isn't paid, but money did move. Decide your policy up front: top-up window, partial refund, or manual review. Don't discover this at 2am during a sale.
- Late deposits. A confirmation lands after the invoice expired. The
invoice.late_depositevent exists precisely so you can reconcile a payment that arrived too late instead of silently eating it. - The closed-tab buyer. Plenty of customers pay and immediately close the tab before the
redirect_urlever fires. Your confirmation email has to be driven by the webhook, not by the buyer landing back on your store. If your "thanks for your order" email depends on the redirect, a third of your real payers never get it.
This is the whole argument for treating the webhook as the source of truth. The buyer's browser is a hint. The signed event is the fact.
What we'd tell you to do today
Start with the hosted-checkout link, not the API. You'll have crypto live on the store this afternoon and you can graduate to the API flow once you know your order edge cases. Anchor every invoice to fiat. Stand up the webhook endpoint, verify on raw bytes, make it idempotent, and test it against a real invoice.paid and a duplicate of the same event before you call it done.
The deeper version of all of this — the e-commerce-specific patterns, the chargeback math, the refund flows — lives on the e-commerce use-case page. But the one-line version is the line at the top: there's no plugin, and the absence of one is the honest part. What you build instead is sturdier than anything that would have slotted into Shopify's locked checkout, because it treats settlement like settlement instead of like a card swipe.
S. Brandt, halfin solutions