Stripe's dashboard is built for support and debugging, not for the kind of filtering and pivoting a finance team needs, so pulling recent charges into a spreadsheet with Apps Script gives that team a working copy they can sort and annotate freely.
This tutorial authenticates against the Stripe API using HTTP Basic authentication, where the secret key is sent as the username and the password is left blank, then lists recent charges and writes them into a Payments tab.
Stripe returns amounts as integers in the currency's smallest unit, so the script divides by one hundred before writing a value, which matters for any downstream formula that expects dollars rather than cents.
The secret key lives in Script Properties rather than in the script body, which is the same precaution Stripe recommends in its own API key handling guidance and keeps the key out of anything that gets copied or shared.
Need this built? Hire a Google Apps Script developer →
Sheet / project setup
| Config | Value | Purpose |
|---|---|---|
| STRIPE_SECRET_KEY | Script property sk_live_… | Basic auth username |
| Endpoint | https://api.stripe.com/v1/charges | List charges |
| Sheet | Payments | id, amount, currency, status |
What it does
listStripeCharges requests the most recent twenty-five charges from the /v1/charges endpoint and writes each one as a row containing the charge ID, amount, currency, status, customer ID, and creation timestamp.
Prerequisites
Basic authentication in Stripe's REST API takes the form secretKey colon nothing, base64 encoded and sent as an Authorization header, which is why the script concatenates the key with a trailing colon before calling Utilities.base64Encode.
Walkthrough
Copy a restricted or secret API key from the Stripe dashboard's Developers section, scope it to read-only charge access if you only need reporting, and store it under STRIPE_SECRET_KEY in Script Properties.
Edge cases
The function builds the Basic auth header once, sends a GET request with a limit query parameter, and converts the returned cents-based amount field into a decimal value before the row ever reaches the sheet.
Testing
Any response code other than 200 causes the function to throw with Stripe's own error body included, which typically names the offending parameter or explains that the key lacks the requested permission.
Hardening
Use a Stripe test-mode secret key first so the Payments tab fills with sandbox charges, confirm the amount column shows dollars rather than cents, and only then swap in the live secret key.
Variations
For stores processing more than twenty-five charges between runs, page through results using the starting_after parameter with the last charge ID from the previous page, since this tutorial's single request only covers the most recent page.
Full code: listStripeCharges()
Store the secret key in Script Properties, start with a test-mode key, and confirm the Payments sheet layout before switching to a live key.
var STRIPE_SECRET_KEY_PROPERTY = 'STRIPE_SECRET_KEY';
function listStripeCharges() {
var secretKey = PropertiesService.getScriptProperties().getProperty(STRIPE_SECRET_KEY_PROPERTY);
if (!secretKey) throw new Error('Missing STRIPE_SECRET_KEY script property.');
var authHeader = 'Basic ' + Utilities.base64Encode(secretKey + ':');
var url = 'https://api.stripe.com/v1/charges?limit=25';
var options = {
method: 'get',
headers: { Authorization: authHeader },
muteHttpExceptions: true
};
var response = UrlFetchApp.fetch(url, options);
if (response.getResponseCode() !== 200) {
throw new Error('Stripe request failed: ' + response.getContentText());
}
var charges = JSON.parse(response.getContentText()).data;
var sheet = SpreadsheetApp.getActive().getSheetByName('Payments') || SpreadsheetApp.getActive().insertSheet('Payments');
sheet.clearContents();
sheet.appendRow(['Charge ID', 'Amount', 'Currency', 'Status', 'Customer', 'Created']);
var rows = charges.map(function (charge) {
var created = new Date(charge.created * 1000);
return [charge.id, charge.amount / 100, charge.currency, charge.status, charge.customer || '', created];
});
if (rows.length > 0) {
sheet.getRange(2, 1, rows.length, rows[0].length).setValues(rows);
}
}- Line 4: Reads the Stripe secret key from Script Properties.
- Line 6: Builds the Basic auth header from the secret key plus a trailing colon.
- Line 14: Checks the response code before parsing the charges array.
- Line 19: Gets or creates the Payments sheet.
- Line 23: Converts the cents-based amount field into a decimal dollar value.
- Line 27: Writes every charge row in a single setValues call.
Deploy this example
- 01
Open Apps Script
In the bound spreadsheet: Extensions → Apps Script. For standalone projects, create one at script.google.com and link your Sheet by ID.
- 02
Paste and save
Add a .gs file, paste the code below, rename constants at the top (sheet names, column letters, API property keys), then save.
- 03
Authorize once
Run the main function from the editor. Accept OAuth scopes when prompted — triggers cannot run until authorization succeeds once.
- 04
Add the trigger
Triggers → Add trigger → choose the handler function and event (time-driven, on edit, or on form submit). Delete test triggers before production.
Before you run: list Stripe charges
- 1Stripe secret key copied from the dashboard's Developers section
- 2Key stored under STRIPE_SECRET_KEY in Script Properties
- 3Test-mode key used for the first run
- 4Payments sheet exists or the script has permission to create it
- 5Amount column confirmed to show dollars, not cents
- 6Response code checked before reading the data array
- 7Pagination plan noted if more than twenty-five charges occur between runs
Frequently asked questions
Stripe's REST API uses HTTP Basic authentication where the API key serves as the entire credential, so the convention is key followed by a colon and an empty password before base64 encoding.
A restricted key scoped to read-only charge access is safer for a reporting script like this one, since it cannot create refunds or charges even if the Script Properties value were ever exposed.
Stripe represents money as integer cents to avoid floating-point rounding issues, so the script divides by one hundred to show the value the way a spreadsheet user expects to see it.
Yes, the same authentication and request pattern works against /v1/payment_intents; only the endpoint URL and the fields you read off each returned object change.
Filter the parsed charges array on charge.status === 'succeeded' before mapping rows, or add a status query parameter if the endpoint you are calling supports it.
Stripe returns a 401 response with an error object describing an invalid API key, and the script throws that message directly so you see the real cause instead of a generic failure.