Sync Stripe charges, invoices and subscriptions to Google Sheets, including the amounts in cents that make revenue look 100x too big and the test-mode data that never appears.
Stripe's dashboard exports to CSV in about two minutes, which answers most one-off finance requests. For recurring reporting you go through the API, and two things quietly produce wrong numbers on the first attempt: amounts arrive as integers in cents, so revenue looks a hundred times too big, and a test-mode key returns an empty or fake dataset that looks like a broken connection. This guide covers both, plus pagination, expansion, and which key to use.
The two-minute option: dashboard CSV export
In the Stripe dashboard open Payments, Invoices or Balance → Payouts, set a date range, and click Export. You can choose which columns to include, and large exports are emailed rather than downloaded directly. Import the file into Sheets via File → Import.
For reconciliation specifically, export balance transactions rather than charges. Balance transactions include fees, refunds, disputes and payout timing, which is what actually reconciles against your bank; a charges export shows gross amounts and will not tie out.
Stripe to Google Sheets for free (Apps Script + API)
Create a restricted key under Developers → API keys rather than using your secret key. Grant read-only permissions on just the resources you need. A restricted key that leaks through a shared sheet cannot issue refunds or create charges; a secret key can do both.
Stripe's API is form-encoded rather than JSON for requests, uses the key as basic-auth username with an empty password, and paginates by cursor: read has_more, then pass the last object's ID as starting_after.
function importStripeCharges() { const KEY = "rk_live_XXXXXXXXXXXX"; // restricted, read-only // Live keys see live data only; a test key returns test data only. const headers = { Authorization: "Basic " + Utilities.base64Encode(KEY + ":") }; // Zero-decimal currencies are already whole units. Dividing these by 100 // understates revenue by 100x, the mirror of the usual cents mistake. const ZERO_DECIMAL = ["JPY","KRW","VND","CLP","ISK","BIF","DJF","GNF", "KMF","MGA","PYG","RWF","UGX","VUV","XAF","XOF","XPF"]; const rows = []; let startingAfter = null; do { let url = "https://api.stripe.com/v1/charges?limit=100" + "&expand[]=data.customer"; // else customer is just an ID string if (startingAfter) url += "&starting_after=" + startingAfter; const res = JSON.parse( UrlFetchApp.fetch(url, { headers: headers, muteHttpExceptions: true }) .getContentText() ); if (res.error) throw new Error(res.error.message); res.data.forEach((c) => { const cur = (c.currency || "").toUpperCase(); const amount = ZERO_DECIMAL.indexOf(cur) === -1 ? c.amount / 100 : c.amount; rows.push([ c.id, new Date(c.created * 1000), // epoch seconds, not milliseconds amount, cur, c.status, c.customer ? (c.customer.email || c.customer.id) : "", c.refunded, ]); }); startingAfter = res.has_more ? res.data[res.data.length - 1].id : null; } while (startingAfter); const header = ["Charge ID","Created","Amount","Currency","Status","Customer","Refunded"]; const ss = SpreadsheetApp.getActiveSpreadsheet(); const sheet = ss.getSheetByName("Stripe") || ss.insertSheet("Stripe"); sheet.clearContents(); sheet.getRange(1, 1, 1, header.length).setValues([header]); if (rows.length) { sheet.getRange(2, 1, rows.length, header.length).setValues(rows); }}Two more details in that code. Stripe timestamps are Unix epoch seconds, so they need multiplying by 1000 before JavaScript reads them as dates; forgetting produces dates in 1970. And expand[]=data.customer is what turns the customer field from an opaque ID into an object with an email on it.
Limits: Apps Script's six-minute cap means high-volume accounts need chunking by date using the created[gte] and created[lte] filters. Stripe is read-only here by design, and this is one connector where write-back is not something you want.
Stripe Sigma
Sigma lets you run SQL directly against your Stripe data inside the dashboard, with scheduled queries and CSV output. If your questions are genuinely SQL-shaped, joining charges to subscriptions to customers with window functions, Sigma answers them more cleanly than assembling the same thing from API calls. It is billed on usage, and getting results into Sheets still means exporting or forwarding the output, so it complements a Sheets connector rather than replacing one.
The Brooked method
Brooked handles the cursor pagination, converts amounts using each charge's own currency so zero-decimal currencies stay correct, converts epoch timestamps to real dates, and expands customers so you get emails rather than IDs. Charges, invoices, subscriptions, customers and balance transactions can all land in one spreadsheet on a single schedule.
Step 1: Install Brooked
Install Brooked from the Google Workspace Marketplace. Free tier includes 100 imports per month, no credit card required.
Step 2: Connect Stripe
Choose Stripe in Brooked and paste a restricted, read-only API key. Confirm it is a live key if you want live data. Then pick the object you want, charges, invoices, subscriptions, customers or balance transactions, and import.
Step 3: Schedule the refresh
Set the import to re-run hourly, daily or weekly so MRR and revenue tables rebuild themselves. A scheduled re-read also captures refunds and disputes raised against older charges, which an append-only webhook feed would never revisit.
Method comparison
| Method | Cost | Setup | Refresh | Two-way | Best for |
|---|---|---|---|---|---|
| Stripe dashboard CSV export | Free | 2 min | Manual | No | A one-off payouts or charges export |
| Apps Script + Stripe API | Free | 20 to 35 min | Time-driven trigger | No | Custom objects and fields, no budget |
| Stripe Sigma | Paid, usage-based | SQL knowledge needed | Scheduled queries | No | Complex SQL over Stripe data inside Stripe |
| Brooked | Free tier · Pro $29/user/mo | ~2 min | Scheduled (hourly, daily, weekly) | No | Scheduled finance reporting in Sheets |
Common use cases
- MRR and ARR reporting: build recurring revenue tables from subscriptions rather than eyeballing the dashboard
- Revenue reconciliation: pull balance transactions with fees and payouts to tie out against the bank
- Churn analysis: track cancelled subscriptions by plan, cohort and reason
- Failed payment recovery: list past-due invoices for the team chasing them
- Board reporting: combine Stripe revenue with pipeline or spend data already in the sheet
Troubleshooting common issues
Related Articles
- NetSuite Chrome Extension for Google Sheets
- How to Connect Sage Intacct to Google Sheets
- How to Consolidate Multiple QuickBooks Companies in One Google Sheet
Frequently asked questions
Why are my Stripe amounts 100 times too large?
Stripe returns amounts as integers in the smallest currency unit, so $49.99 arrives as 4999. Divide by 100 for most currencies, but not all: zero-decimal currencies including JPY and KRW are already whole units, and dividing those by 100 makes revenue look 100 times too small. Check the currency field rather than dividing everything blindly.
Can I connect Stripe to Google Sheets for free?
Yes. The Stripe dashboard exports payments, payouts, invoices and more to CSV at no cost, which covers one-off requests. For refreshing data, the Apps Script method in this guide uses a free restricted API key and a free time-driven trigger. Brooked's free tier covers 100 imports a month.
Which Stripe API key should I use for a spreadsheet?
A restricted key, not your secret key. In the dashboard under Developers → API keys you can create a restricted key granting read-only access to just the resources you need, such as charges, invoices and subscriptions. If that key leaks through a shared spreadsheet or script, it cannot move money or change anything, which a full secret key absolutely can.
Why is my Stripe data missing or empty?
Almost always test versus live mode. Stripe keeps the two entirely separate, and a test-mode key (sk_test_ or rk_test_) returns only test data, which is usually empty or full of dummy payments. Live data needs a live key. The dashboard toggle does not affect which data an API key can see; the key itself determines that.
How do I get more than 100 records from Stripe?
100 is the maximum limit on list endpoints. Stripe uses cursor pagination: read has_more from the response, take the id of the last object, and pass it as starting_after on the next request. Official SDKs offer auto-pagination that does this for you, but a raw Apps Script fetch needs the loop written out.
How do I get customer details alongside charges?
Use expand. Stripe returns related objects as ID strings by default, so a charge shows a customer ID rather than an email. Adding expand[]=data.customer inflates those into full objects in the same response. Expansion is limited to four levels deep and each expansion adds to the response size, so expand only what you actually need.
Connect Stripe to Google Sheets today.
Free tier: 100 imports per month, no credit card required. Or see the full Stripe integration details.
Install Brooked free →

