Shopify

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.

BetaThe official Shopify App is in private beta and is not yet listed in the Shopify App Store. The drop-in button and the SDK below are production-ready today.
Option A

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.

cart.liquidliquid
{%- 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.

blockpay-button.liquidliquid
<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.

Option B

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

bashbash
npm install @blockpay/shopify @blockpay/checkout

Webhook 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.

webhooks.blockpay.tsxtypescript
// app/routes/webhooks.blockpay.tsx
import 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.

process_paymenttypescript
// 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);
Reference

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 JSON

Fires 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.

payloadjson
{
"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 JSON

Fires 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.

payloadjson
{
"type": "invoice.paid",
"data": {
"invoiceId": "inv_01HE2...",
"amount": "49.00",
"currency": "USDC",
"chainKey": "arc-testnet",
"txHash": "0x9b1c...e3f0",
"settledAt": 1715817600
}
}
payment.receivedPOST JSON

Fires for each on-chain transfer attributed to the invoice. A short-paid invoice can produce many of these before invoice.paid fires.

payloadjson
{
"type": "payment.received",
"data": {
"invoiceId": "inv_01HE2...",
"amount": "49.00",
"currency": "USDC",
"chainKey": "arc-testnet",
"fromAddress": "0xCustomerWallet",
"txHash": "0x9b1c...e3f0",
"confirmations": 1
}
}
invoice.expiredPOST JSON

Fires when an invoice passes its TTL with no settled transfer. Surface this back to the Shopify order as a cancelled payment attempt.

payloadjson
{
"type": "invoice.expired",
"data": {
"invoiceId": "inv_01HE2...",
"expiredAt": 1715819400,
"reason": "ttl"
}
}
webhook.testPOST JSON

Sent from the BlockPay dashboard when you click Send test event. Use it to confirm signature verification before you trust live events.

payloadjson
{
"type": "webhook.test",
"data": {
"deliveredAt": 1715820000,
"endpoint": "https://store.example.com/webhooks/blockpay"
}
}