Methodology
How Folio captures and verifies POs.
This page documents the technical flow Folio uses to collect a buyer's purchase order PDF after Shopify checkout and link it to the correct order, without requiring Shopify Plus.
Architecture overview
Folio runs as a Cloudflare Worker at folio.maselabs.com. It serves a static marketing site and exposes API routes under /api/*. Order and document data are stored in a Neon Postgres database accessed over HTTP via the @neondatabase/serverless driver. The Shopify app extension renders on the Thank You and Order status pages.
Step 1: Order creation webhook
When a store installs Folio, Shopify registers an orders/create webhook pointing to /api/webhooks/orders-create. Each request is verified using HMAC-SHA256 with the app's shared secret before any database work.
The webhook payload includes checkout_token, payment_gateway_names, and the order ID. Folio only creates a po_orders record when checkout_token is present and one of the payment gateway names normalizes to Purchase Order (case- and punctuation-insensitive). This ensures the upload block only appears for manual PO orders.
Step 2: Thank You / Order status page extension
Folio's purchase.thank-you.block.render extension mounts after checkout. It reads the checkout token from Shopify's checkoutToken signal (which may populate after the extension first renders) and polls /api/order-status with a signed session token until the order is confirmed as a purchase order, the buyer used a different payment method, or the maximum retry limit is reached.
Step 3: PDF upload
When the buyer selects a PDF, the extension validates the file client-side (.pdf extension, application/pdf type, %PDF- magic bytes, ≤ 5 MB). On upload, it sends the file and checkout token to /api/uploads with the same session token for authentication.
The Worker re-validates the PDF server-side, enforces the 5 MB limit by bounding the request body stream, and updates the po_orders row matching the shop and checkout token, setting status = 'received' and storing the file bytes in document_data.
Step 4: Merchant inbox
The embedded app at /app loads the merchant's PO orders via /api/orders (paginated, cursor-based). It shows the order name, document filename, size, and status badge. For received orders, the merchant can download the PDF from /api/orders/:id/document and mark it reviewed via /api/orders/:id/review, which sets status = 'reviewed'.
Step 5: Readiness verification
/api/readiness reports three checks: database round-trip, schema presence (po_orders table), and whether at least one real PO order has been uploaded and marked reviewed. The endpoint returns ready: true only when all three pass.
Data model
The po_orders table (see migrations/0001_init.sql) stores one row per PO order per shop, keyed by (shop, checkout_token) with a unique constraint on (shop, order_id). Columns include the document name, size, binary data, and status (missing | received | reviewed). An index on (shop, status, updated_at) supports the merchant inbox query.
Security boundaries
- All API routes require a valid Shopify session token (JWT signed by the shop's secret, audience-checked against the app's Client ID).
- Webhooks are verified with HMAC before processing.
- Order data is scoped to the shop; no cross-shop access is possible.
- Compliance webhooks (
customers/redact,shop/redact) redact or delete the shop's data on request. - PDF content is never parsed or inspected beyond the magic-byte check.
Why not Shopify Files?
Folio stores PDFs in Postgres (BYTEA) rather than Shopify Files because: (1) it keeps the document with its order record atomically, (2) it avoids additional API calls and scope requests, (3) the 5 MB limit fits comfortably in a row, and (4) it satisfies data residency and deletion requirements without a second storage system.
Limits and constraints
- PDFs ≤ 5 MB; larger files are rejected at the edge.
- Webhook bodies ≤ 1 MB; oversized payloads return 413 before signature verification.
- Session tokens must be
HS256, audience-matched, issued by the shop's admin, and not expired. - The extension polls up to 8 times over ~8 seconds; if the webhook hasn't created the order record yet, the buyer sees a waiting message.
- Folio does not process, validate, or approve the PO; it only collects the file and tracks status.
Test procedure
- Install Folio on a regular-plan Shopify dev store (not Plus).
- Add a manual payment method named exactly Purchase Order.
- Place a test order using that payment method.
- On the Thank You page, upload a valid PDF.
- Open the embedded app (
/appin Shopify Admin) and confirm the order shows Received with the filename. - Download the PDF to verify integrity.
- Mark the order Reviewed.
- Confirm
/api/readinessreturnsready: true.