Spraay Shopify Self-Host

Deploy the open-source Spraay Shopify app — batch USDC payouts on Base — for your store in ~15 minutes. Walks through the custom app setup, Railway deploy, env vars, and Supabase d…

Plag

@plagtech

Install

$ openclaw skills install @plagtech/spraay-shopify-selfhost

Self-Host the Spraay Shopify App

Walk a merchant from zero to a working batch-USDC-payouts page inside their Shopify admin in ~15 minutes. The app is open source (MIT), non-custodial, and requests zero Shopify scopes — it cannot read or write any store data.

Repo: https://github.com/plagtech/spraay-shopify

Stack at a glance

  • Remix + @shopify/shopify-app-remix, Vite, Polaris UI (fully embedded in admin)
  • wagmi v3 + viem v2 — MetaMask and Coinbase Smart Wallet
  • Base (chain 8453), USDC 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
  • Batch contract 0x1646452F98E36A3c9Cfc3eDD8868221E207B5eEC (verified; never substitute another address)
  • Supabase PostgreSQL via Prisma, isolated in a shopify schema
  • Railway hosting (Dockerfile, node:20-alpine)

Prerequisites checklist

Confirm the user has (or help them create) each of these before starting:

  1. A Shopify store (dev store is fine for testing)
  2. A free Shopify Partners account → https://dev.shopify.com
  3. A Railway account (hobby tier is enough)
  4. A Supabase project (free tier is enough)
  5. A wallet holding USDC on Base (MetaMask or Coinbase Smart Wallet)

Step 1 — Fork and deploy to Railway

  1. Fork plagtech/spraay-shopify (Node 20+ required if running outside Docker: >=20.19 <22 || >=22.12).
  2. Easiest path: the Deploy on Railway button in the README — the template pre-prompts for every required env var, with PORT defaulted to 3000. Manual path: Railway → New Project → Deploy from GitHub repo → select the fork; the repo ships a railway.json and Dockerfile, so the build is automatic.
  3. Generate a public domain for the service (Settings → Networking). Note the URL, e.g. your-app.up.railway.app.

The app will crash-loop until env vars are set — that's expected. Continue.

Step 2 — Create the custom app in Shopify

  1. Go to https://dev.shopify.com → Apps → Create appCustom distribution (single merchant). Custom distribution is Shopify's intended path for merchant-specific tooling — no App Store review involved.
  2. Set App URL to the Railway URL from Step 1 (https, no trailing slash).
  3. Add all three OAuth redirect URLs (each prefixed by the App URL):
    • https://your-domain/auth/callback
    • https://your-domain/auth/shopify/callback
    • https://your-domain/api/auth/callback
  4. Copy the Client ID and Client Secret.

Step 3 — Environment variables

Set these in Railway → service → Variables. The repo's .env.example documents each one; the authoritative reference:

VariableValueNotes
SHOPIFY_API_KEYApp Client IDPublic — ships in the browser bundle; safe to share
SHOPIFY_API_SECRETApp Client SecretSecret — signs webhook HMACs and OAuth. Never commit or paste into chat logs
SHOPIFY_APP_URLhttps://your-app.up.railway.appMust exactly match the App URL in Shopify settings; no trailing slash
SCOPES(empty)Intentional. The app never touches store data. Leave blank unless the fork adds Shopify API usage — then keep it in sync with shopify.app.toml
DATABASE_URLpostgresql://user:pass@host:6543/postgres?schema=shopify&pgbouncer=trueSupabase pooled connection (transaction pooler, port 6543) — used at runtime
DIRECT_URLpostgresql://user:pass@host:5432/postgres?schema=shopifySupabase direct connection (port 5432) — used by prisma migrate
NODE_ENVproduction
PORT3000Load-bearing. See gotcha below
SHOP_CUSTOM_DOMAIN(optional)Only if the dev store uses a custom domain

The PORT=3000 gotcha (most common failure): the Dockerfile's EXPOSE 3000 sets Railway's domain target port to 3000. If PORT is unset, Railway injects PORT=8080, the app binds :8080, the proxy still routes to :3000, and every route returns 502. If the user reports 502s on all routes, check this first.

Database notes: keep ?schema=shopify on both URLs so the app's tables stay isolated from anything else in the Supabase project. Both URLs point at the same database — only the port and pooling differ.

After saving variables, redeploy. Migrations run automatically on boot (prisma generate && prisma migrate deploy, then remix-serve). A clean boot — migrate succeeds and remix-serve prints its listen URL — means Steps 1–3 are done.

Diagnostic tip: a 502 while Railway shows the deployment "Online" is a port/proxy mismatch, not a crash. If the logs show a successful migrate and a listening server, fix PORT, don't debug the app.

Step 4 — Install and test

  1. From the Dev Dashboard, install the app on the store. Complete the OAuth prompt.

  2. Open the app from the Shopify admin sidebar — the Polaris UI should render embedded.

  3. Run a small real test: CSV with 1–2 recipients and small amounts. Header row is optional (first two columns are assumed address, amount); optional extra columns name, email, memo are supported, and the payout screen has a Download CSV template link and Load sample data button. Example:

    wallet_address,amount
    0xYourOtherWallet...,1.00
    
  4. Review screen shows recipients, total, and the 0.3% protocol fee → connect wallet → approve USDC → execute → confirm the BaseScan link resolves.

Optional — local development

For hacking on the app itself (not needed for a production deploy): npm install && npm run dev. The Shopify CLI creates a tunnel and injects SHOPIFY_API_KEY, SHOPIFY_API_SECRET, and SHOPIFY_APP_URL, so local .env only needs DATABASE_URL and DIRECT_URL.

Optional — custom domain

Point a subdomain (e.g. payouts.yourstore.com) at the Railway service via CNAME, wait for the SSL cert to issue, then update SHOPIFY_APP_URL and the App URL + redirect URLs in Shopify settings to match. All three must agree or OAuth breaks.

Troubleshooting

SymptomLikely causeFix
502 on every routeRailway injected PORT=8080Pin PORT=3000
OAuth redirect error / "invalid redirect_uri"App URL mismatchSHOPIFY_APP_URL, Shopify App URL, and redirect URL prefixes must be identical (scheme, host, no trailing slash)
Prisma migrate failsUsing pooled URL for migrationsMigrations need DIRECT_URL (port 5432); runtime uses DATABASE_URL (port 6543)
App loads outside admin but not embeddedWrong App URL or missing HTTPSEmbedded apps require the exact HTTPS App URL configured in Shopify
Wallet won't connectWrong networkThe app targets Base (8453); switch network in the wallet

Security posture (tell the user)

  • Zero scopes: the app requests no Shopify permissions — it structurally cannot access orders, customers, or products.
  • Non-custodial: USDC moves only when the merchant's own wallet signs, in their browser. The server never holds keys or funds.
  • Self-hosted: credentials, database, and infrastructure belong to the merchant.
  • The 0.3% protocol fee is itemized on the review screen and collected on-chain by the batch contract. It is contract-enforced on Base, not app code — self-hosting or forking does not remove it, and there is no subscription or plan.
  • Security issues → report privately to support@spraay.app, not a public issue.

Related

Top skills in this category

API Gateway

@byungkyu

Call third-party APIs through the Maton gateway, which injects the credential for an app the user has already connected. Use this skill when the user names a connected app and a concrete action in it - read a mailbox, query a CRM, file an issue, update a spreadsheet, run a query through a connected

39486k

Feishu Bridge

@alexanys

Connect a Feishu (Lark) bot to Clawdbot via WebSocket long-connection. No public server, domain, or ngrok required. Use when setting up Feishu/Lark as a messaging channel, troubleshooting the Feishu bridge, or managing the bridge service (start/stop/logs). Covers bot creation on Feishu Open Platform, credential setup, bridge startup, macOS launchd auto-restart, and group chat behavior tuning.

811k

Attio

@byungkyu

Attio API integration with managed OAuth. Manage CRM data including people, companies, and custom objects. Use this skill when users want to create, read, update, or delete records in Attio, manage tasks, notes, comments, lists, meetings, or query CRM data. For other third party apps, use the api-gateway skill (https://clawhub.ai/byungkyu/api-gateway). Calls run through the `maton` CLI with OAuth login; default to read and list calls, and confirm every write or new connection with the user.

314k

Proxmox

@weird-aftertaste

Manage Proxmox VE clusters via REST API. Use when user asks to list, start, stop, restart VMs or LXC containers, check node status, create snapshots, view tasks, or manage Proxmox infrastructure. Requires API token or credentials configured.

64.6k

keap

@byungkyu

Keap API integration with managed OAuth. Manage contacts, companies, tags, tasks, orders, opportunities, and campaigns for CRM and marketing automation. Use this skill when users want to create and manage contacts, apply tags, track opportunities, or automate marketing workflows in Keap. For other third party apps, use the api-gateway skill (https://clawhub.ai/byungkyu/api-gateway). Requires network access and valid Maton API key. Calls run through the `maton` CLI with OAuth login; default to read and list calls, and confirm every write or new connection with the user.

34.6k