DEV Community

eliaskress
eliaskress

Posted on • Originally published at developer.usepopup.com

Building a financial dashboard with Stripe, Shopify, and Plaid without writing a reconciliation engine

Your data lives in three places

If you run a business that sells through Shopify and processes payments with Stripe, your financial data is split across three systems. Stripe knows your payment volume and fees. Shopify knows your orders, refunds, and its own payouts. Your bank knows what actually landed in your account and what you spent.

Building a dashboard that combines all three means writing OAuth flows, managing API keys, syncing data on a schedule, normalizing amounts (Stripe uses cents, Plaid uses dollars with negative signs for deposits), and building matching logic to figure out which bank deposit corresponds to which payout. That is months of work before you display a single number.

FlowCheck handles all of that. You connect your accounts, and every endpoint returns unified data. Here is how to set it up from scratch.

Step 1: Get an API key

Register with your email. You will get 100 free credits and a sandbox key to test with.

curl -X POST https://developer.usepopup.com/api/v0/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email": "you@example.com"}'
Enter fullscreen mode Exit fullscreen mode

Complete the checkout flow, then check your registration status to get your API key:

curl https://developer.usepopup.com/api/v0/auth/register/status?token=YOUR_TOKEN
Enter fullscreen mode Exit fullscreen mode

Save the key. It starts with fc_live_ for production or fc_test_ for sandbox. You will not see it again.

Step 2: Connect Stripe

Create a restricted key in your Stripe dashboard with read access to Payouts and Balance. Then pass it to FlowCheck:

curl -X POST https://developer.usepopup.com/api/v0/connect/stripe \
  -H "Authorization: Bearer fc_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{"restricted_key": "rk_live_your_stripe_key"}'
Enter fullscreen mode Exit fullscreen mode

Or with the SDK:

import { FlowCheck } from "@flowcheck/sdk"

const fc = new FlowCheck("fc_live_your_key")
await fc.connectStripe({ restrictedKey: "rk_live_your_stripe_key" })
Enter fullscreen mode Exit fullscreen mode

FlowCheck encrypts your Stripe key with AES-256-GCM before storing it. Once connected, payout data syncs automatically.

Step 3: Connect Shopify

Create a custom app in your Shopify admin with read access to Orders and Payouts. Pass the shop domain and access token:

await fc.connectShopify({
  shop: "my-store.myshopify.com",
  accessToken: "shpat_your_token"
})
Enter fullscreen mode Exit fullscreen mode

Shopify orders and payouts now show up alongside Stripe data in every FlowCheck endpoint.

Step 4: Connect your bank account

Bank connections use Plaid Link, a two-step flow. First, get a Link token from FlowCheck:

const { data } = await fc.createPlaidLinkToken()
// data.link_token = "link-sandbox-abc123..."

// Use the Plaid Link SDK in your frontend to open the bank selector
// When the user finishes, Plaid gives you a public_token

await fc.exchangePlaidToken({ publicToken: "public-sandbox-xyz..." })
Enter fullscreen mode Exit fullscreen mode

After the exchange, bank transactions sync automatically. Deposits show as negative amounts in Plaid (yes, really), and FlowCheck normalizes everything so you do not have to think about it.

Step 5: Query everything from one endpoint

With all three sources connected, a single call gives you your complete financial picture:

const cashflow = await fc.cashflow("30d")

console.log(`Total inflow: $${cashflow.data.total_inflow / 100}`)
console.log(`Total outflow: $${cashflow.data.total_outflow / 100}`)
console.log(`Net: $${cashflow.data.net / 100}`)

// Daily breakdown
for (const day of cashflow.data.daily) {
  console.log(`${day.date}: +$${day.inflow / 100} / -$${day.outflow / 100}`)
}
Enter fullscreen mode Exit fullscreen mode

The balance endpoint gives you a snapshot across all connected accounts:

curl https://developer.usepopup.com/api/v0/balance \
  -H "Authorization: Bearer fc_live_your_key"
Enter fullscreen mode Exit fullscreen mode
{
  "data": {
    "stripe": { "available": 234100, "pending": 89200 },
    "bank": { "current": 1247800, "available": 1245300 },
    "total_available": 1479400
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 6: Set up webhooks for real-time updates

Instead of polling the API, register a webhook to get notified when payouts match, when discrepancies are detected, or when a refund affects your reconciliation:

await fc.createWebhook({
  url: "https://your-app.com/webhooks/flowcheck",
  events: [
    "payout.matched",
    "payout.discrepancy",
    "payout.missing",
    "refund.detected",
    "balance.threshold"
  ]
})
Enter fullscreen mode Exit fullscreen mode

Every webhook delivery is signed with HMAC-SHA256. Verify the FlowCheck-Signature header before processing.

What you did not have to build

At this point you have a working financial data layer. Here is what FlowCheck handled so you did not have to:

  • Stripe payout sync with automatic backfill and ongoing polling
  • Shopify order and payout sync with the same pattern
  • Plaid bank transaction sync with soft-delete for removed transactions
  • Amount normalization across cents (Stripe), dollars-negative-for-income (Plaid), and Shopify order amounts
  • Reconciliation matching using a 100-point scoring model across amount, date, description, and bank account
  • Discrepancy detection for missing deposits, amount mismatches, and timing anomalies
  • Webhook infrastructure with HMAC signing, retry logic, and automatic endpoint disabling after repeated failures

All of that is code you would have written yourself, debugged across edge cases, and maintained as APIs change. Instead, you made six API calls and you are done.

Get started

Get an API key and connect your first data source. The sandbox is free and comes with test data so you can see real responses without connecting production accounts. The full API reference has examples for every endpoint.

Top comments (0)