Shopify integration.
Two paths in. Drop a theme button into any store in five minutes, or build a full Shopify App on top of @blockpay/shopify with the SDK doing the ugly work.
Drop-in theme button
No app install, no engineering ticket, ~5 minutes.
The fastest way to put a USDC checkout in front of a Shopify customer. You either install the BlockPay App Store entry (one-click), or paste the two snippets below into your theme. The button takes the customer to BlockPay's hosted /pay/<slug> page, so there is nothing to self-host.
1. Render the mount point
Save the snippet below as snippets/blockpay-button.liquid and render it from your cart.liquid template wherever you want the button to appear.
{%- comment -%} Paste into snippets/blockpay-button.liquid, then render it from cart.liquid: {% render 'blockpay-button' %}{%- endcomment -%}<div id="blockpay-button"></div><script src="https://unpkg.com/@blockpay/shopify/theme" defer></script>2. Initialise the button
Add the init script to the same snippet. The API key is pulled from your theme settings — never hard-code it.
<script> window.addEventListener("DOMContentLoaded", function () { BlockPayShopify.mount({ apiKey: "{{ settings.blockpay_api_key }}", cart: window.cart, elementId: "blockpay-button", }); });</script>That is the full integration. The script reads window.cart, asks the BlockPay API for an invoice, and redirects the buyer to the hosted checkout. On payment the buyer is sent back to your store's thank-you URL and your webhook endpoint fires.
Custom Shopify App
For teams building their own embedded Shopify app on BlockPay.
If you maintain your own Shopify App — embedded or headless — install the SDK and let it do the webhook verification, event routing and order-to-invoice mapping. The samples below assume a Remix Shopify App, which is the template the Shopify CLI scaffolds today.
Install
npm install @blockpay/shopify @blockpay/checkoutWebhook route
Mount a single route that receives every BlockPay event and dispatches it via bp.webhooks.routeEvent. The SDK verifies the HMAC signature in constant time before any handler runs.
// app/routes/webhooks.blockpay.tsximport type { ActionFunctionArgs } from "@remix-run/node";import { BlockPayShopify } from "@blockpay/shopify";export async function action({ request }: ActionFunctionArgs) { const raw = await request.text(); const sig = request.headers.get("X-BlockPay-Signature") ?? ""; const bp = new BlockPayShopify({ apiKey: process.env.BLOCKPAY_API_KEY!, shopDomain: "x.myshopify.com", webhookSecret: process.env.BLOCKPAY_WEBHOOK_SECRET!, }); if (!bp.webhooks.verify({ rawBody: raw, signature: sig })) { return new Response("Bad sig", { status: 401 }); } const event = JSON.parse(raw); await bp.webhooks.routeEvent(event, { onPaymentReceived: async (data) => { // mark Shopify order paid via Admin API // data.invoiceId, data.txHash, data.amount }, }); return new Response("ok");}Sync a Shopify order into an invoice
When the buyer picks BlockPay at checkout, hand the Shopify order to the SDK and it returns a ready-to-use invoice with the hosted checkout URL.
// Convert a Shopify order into a BlockPay invoice in one call.const invoice = await bp.app.syncOrderToInvoice(shopifyOrder);// The returned invoice carries the hosted checkout URL.// Redirect the buyer there from your gateway's process_payment step.return redirect(invoice.checkoutUrl);Webhook events
Every event BlockPay can send to your Shopify integration.
Each event arrives as a JSON POST with an X-BlockPay-Signature header. The SDK verifies and types the payload for you, but the raw schema is below for anyone wiring this up by hand.
invoice.createdPOST JSONFires the moment an invoice is generated for a Shopify order. Use this to record the BlockPay invoice id against the Shopify order before the buyer pays.
{ "type": "invoice.created", "data": { "invoiceId": "inv_01HE2...", "amount": "49.00", "currency": "USDC", "chainKey": "arc-testnet", "checkoutUrl": "https://blockpay.dev/pay/inv_01HE2...", "shopifyOrderId": "gid://shopify/Order/1234567890" }}invoice.paidPOST JSONFires once the full invoice amount has settled on-chain and the indexer has reconciled it. This is the event you wire to your fulfilment pipeline.
{ "type": "invoice.paid", "data": { "invoiceId": "inv_01HE2...", "amount": "49.00", "currency": "USDC", "chainKey": "arc-testnet", "txHash": "0x9b1c...e3f0", "settledAt": 1715817600 }}payment.receivedPOST JSONFires for each on-chain transfer attributed to the invoice. A short-paid invoice can produce many of these before invoice.paid fires.
{ "type": "payment.received", "data": { "invoiceId": "inv_01HE2...", "amount": "49.00", "currency": "USDC", "chainKey": "arc-testnet", "fromAddress": "0xCustomerWallet", "txHash": "0x9b1c...e3f0", "confirmations": 1 }}invoice.expiredPOST JSONFires when an invoice passes its TTL with no settled transfer. Surface this back to the Shopify order as a cancelled payment attempt.
{ "type": "invoice.expired", "data": { "invoiceId": "inv_01HE2...", "expiredAt": 1715819400, "reason": "ttl" }}webhook.testPOST JSONSent from the BlockPay dashboard when you click Send test event. Use it to confirm signature verification before you trust live events.
{ "type": "webhook.test", "data": { "deliveredAt": 1715820000, "endpoint": "https://store.example.com/webhooks/blockpay" }}