How to Send Stripe Payments to Power BI

Load Stripe charges into Power BI every hour so revenue, refunds and payment failures sit in the same reports as the rest of your business data, with no CSV exports and no gateway.

What This Integration Does

Power BI has no built-in Stripe connector, which is why so many teams export CSVs from the Stripe Dashboard every Monday. This Spojit workflow replaces the export. Every hour it reads the Stripe charges created since the last run, reshapes them into clean rows (amounts in dollars rather than cents, readable dates, status, refund totals, customer email) and pushes them into a Power BI push dataset. Reports on that dataset update as rows arrive, so a revenue dashboard is never more than an hour behind Stripe.

Each run asks Stripe only for charges created in a window slightly longer than the schedule interval, and drops any charge whose ID was already sent, so you never double-count revenue. Pushes append rows; a charge refunded later is picked up by the refunds pass described in Step 6.

Prerequisites

  • A Stripe connection in Spojit using a restricted API key with read access to Charges (and Refunds, for Step 6).
  • A Power BI HTTP Requests connection using OAuth 2.0 and a push dataset. If you have not set these up yet, follow the "How to Push JSON Data into Power BI from Any Workflow" tutorial in this help center first. Define a Payments table with the columns charge_id (String), created_at (DateTime), amount (Double), currency (String), status (String), amount_refunded (Double), customer_email (String) and description (String).

Step 1: Schedule Trigger

Add a Trigger node with type Schedule and cron 0 * * * * (hourly), in your finance team's timezone.

Step 2: Work Out the Time Window

Stripe filters by Unix timestamps. Add a Connector node on Date & Time with subtract to take 75 minutes off the trigger time, then a second step with unix to turn that into seconds. Name the second step since; the timestamp is in {{ since.unix }}. The extra 15 minutes overlap the previous run so a charge created at the boundary is never missed.

Step 3: Fetch Charges from Stripe

Add a Connector node on Stripe in Direct Mode with raw-api-request:

  • method: GET
  • path: /charges?created[gte]={{ since.unix }}&limit=100

Name the output charges. Stripe returns up to 100 charges with has_more. If you take more than 100 payments an hour, wrap the call in a Loop in While mode that repeats with starting_after set to the last charge ID until has_more is false.

Step 4: Shape the Rows

Add a Transform node with Iterate over set to the charge list (run Step 3 once to copy the exact path, usually under data). Map each charge:

{
  "charge_id": "{{ item.id }}",
  "created_at": "(item.created converted to ISO 8601)",
  "amount": "(item.amount divided by 100)",
  "currency": "{{ item.currency }}",
  "status": "{{ item.status }}",
  "amount_refunded": "(item.amount_refunded divided by 100)",
  "customer_email": "{{ item.billing_details.email }}",
  "description": "{{ item.description }}"
}

Stripe amounts are in the smallest currency unit (cents), so divide by 100 with the Math Tools connector, and convert created from Unix seconds with the Date & Time unix tool. Zero-decimal currencies such as JPY are already whole units; skip the division for those. If you would rather not wire the conversions field by field, a Code Runner step with a few lines of JavaScript does the whole mapping in one place.

Step 5: Push to Power BI

Add a Connector node on your Power BI HTTP connection with http-post:

  • url: /groups/YOUR_WORKSPACE_ID/datasets/YOUR_DATASET_ID/tables/Payments/rows
  • body: { "rows": {{ rows }} }

A 200 status means the rows are in. To avoid sending the same charge twice when windows overlap, keep the IDs you sent in the last run (for example in a small MySQL table or a File Store file) and filter them out before this step.

Step 6: Track Refunds

Refunds happen after the charge, so a separate table works better than editing old rows. Repeat Steps 3 to 5 against /refunds?created[gte]={{ since.unix }}&limit=100 into a Refunds table (refund_id, charge_id, created_at, amount, reason). In Power BI, net revenue is then SUM(Payments[amount]) - SUM(Refunds[amount]).

Tips

  • Balance transactions for accounting: if finance needs fees and payouts, read /balance_transactions instead of charges. Each balance transaction carries amount, fee and net.
  • Subscriptions: for MRR reporting, add a daily pass over the list-subscriptions tool and push active subscription counts and amounts to a separate table.
  • Backfill once: to load history, run the workflow manually with since set to the start of the year and the While loop enabled. Push datasets keep 200,000 rows with basicFIFO retention.
  • Currency: keep currency as its own column and filter visuals by it. Converting currencies belongs in Power BI or your finance system, not in the feed.

Common Pitfalls

  • Cents versus dollars: forgetting to divide by 100 makes revenue look 100 times larger. Check the first push against the Stripe Dashboard.
  • Failed charges count as rows: Stripe returns failed and pending charges too. Keep them (they are useful for failure-rate visuals) but filter status = "succeeded" in revenue measures.
  • Test mode keys: a sk_test_ or rk_test_ key returns only test-mode charges. Use the live key for the production feed.
  • Push dataset retirement: Microsoft plans to stop creation of new push datasets on 31 October 2027. For a feed you expect to run for years, sync Stripe to a database Power BI reads and keep the push dataset for live tiles.

Testing

Run the workflow manually with a one-day window. Compare the number of rows and the total of amount for succeeded charges with the same day in the Stripe Dashboard's Payments view. When they match, turn the hourly schedule on.

Learn More

Ready to build this?

Start on the free plan: 500 AI credits a month, no card needed.

Start free

Did this answer your question? Thanks for the feedback There was a problem submitting your feedback. Please try again later.