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-selfhostSelf-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
shopifyschema - Railway hosting (Dockerfile, node:20-alpine)
Prerequisites checklist
Confirm the user has (or help them create) each of these before starting:
- A Shopify store (dev store is fine for testing)
- A free Shopify Partners account → https://dev.shopify.com
- A Railway account (hobby tier is enough)
- A Supabase project (free tier is enough)
- A wallet holding USDC on Base (MetaMask or Coinbase Smart Wallet)
Step 1 — Fork and deploy to Railway
- Fork
plagtech/spraay-shopify(Node 20+ required if running outside Docker:>=20.19 <22 || >=22.12). - 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.jsonand Dockerfile, so the build is automatic. - 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
- Go to https://dev.shopify.com → Apps → Create app → Custom distribution (single merchant). Custom distribution is Shopify's intended path for merchant-specific tooling — no App Store review involved.
- Set App URL to the Railway URL from Step 1 (https, no trailing slash).
- Add all three OAuth redirect URLs (each prefixed by the App URL):
https://your-domain/auth/callbackhttps://your-domain/auth/shopify/callbackhttps://your-domain/api/auth/callback
- 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:
| Variable | Value | Notes |
|---|---|---|
SHOPIFY_API_KEY | App Client ID | Public — ships in the browser bundle; safe to share |
SHOPIFY_API_SECRET | App Client Secret | Secret — signs webhook HMACs and OAuth. Never commit or paste into chat logs |
SHOPIFY_APP_URL | https://your-app.up.railway.app | Must 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_URL | postgresql://user:pass@host:6543/postgres?schema=shopify&pgbouncer=true | Supabase pooled connection (transaction pooler, port 6543) — used at runtime |
DIRECT_URL | postgresql://user:pass@host:5432/postgres?schema=shopify | Supabase direct connection (port 5432) — used by prisma migrate |
NODE_ENV | production | |
PORT | 3000 | Load-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
-
From the Dev Dashboard, install the app on the store. Complete the OAuth prompt.
-
Open the app from the Shopify admin sidebar — the Polaris UI should render embedded.
-
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,memoare supported, and the payout screen has a Download CSV template link and Load sample data button. Example:wallet_address,amount 0xYourOtherWallet...,1.00 -
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
| Symptom | Likely cause | Fix |
|---|---|---|
| 502 on every route | Railway injected PORT=8080 | Pin PORT=3000 |
| OAuth redirect error / "invalid redirect_uri" | App URL mismatch | SHOPIFY_APP_URL, Shopify App URL, and redirect URL prefixes must be identical (scheme, host, no trailing slash) |
| Prisma migrate fails | Using pooled URL for migrations | Migrations need DIRECT_URL (port 5432); runtime uses DATABASE_URL (port 6543) |
| App loads outside admin but not embedded | Wrong App URL or missing HTTPS | Embedded apps require the exact HTTPS App URL configured in Shopify |
| Wallet won't connect | Wrong network | The 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
- Running payouts after deployment →
shopify-batch-payoutsskill - Protocol docs → https://docs.spraay.app
Top skills in this category
API Gateway
@byungkyuCall 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
Feishu Bridge
@alexanysConnect 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.
Attio
@byungkyuAttio 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.
Proxmox
@weird-aftertasteManage 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.
keap
@byungkyuKeap 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.