How to Automate Google Sheets for Prop Firm Breach Logs
How to automate Google Sheets so MT5 breach alerts land as timestamped rows through a webhook, giving you a full audit trail of near-misses and blocks.

To automate Google Sheets for a trading audit trail, publish an Apps Script web app, point your MT5 breach alerts at its URL as a webhook, and append one timestamped row per event. No copying from the terminal log, no screenshots. Every near-miss, block and hard breach ends up in a sheet you can filter, chart and review later.
Why a sheet beats the terminal log
The MT5 Experts and Journal tabs roll over, get cleared, and live on one machine. If you trade a VPS, a laptop and a phone, the evidence of what yur rules did on Tuesday is scattered. A Google Sheet is the opposite: one place, searchable, shareable with a mentor, and still there after you rebuild the VPS.
In our Prop-Firm Guardian EA setup, the EA is configured to your firm's exact limits: daily loss, max drawdown, news blackout and lot caps. It sends a breach alert whenever one of those rules fires. The webhook step described here sits right after that alert. The EA enforces, the webhook records, and the sheet is where you look afterward to see how close you were running to each limit.
What the webhook payload looks like
An alert is only useful in a sheet if each field has its own column. Send flat JSON, not a text blob. Here is an illustrative payload; the exact field names get agreed with you during setup.
{
"event_id": "5012345-20261028-143207-01",
"account": 5012345,
"server_time": "2026-10-28 14:32:07",
"event": "BLOCK",
"rule": "daily_loss",
"limit": 500.00,
"value": 462.10,
"pct_of_limit": 92.4,
"action": "entry_blocked",
"symbol": "XAUUSD",
"token": "replace-with-your-secret"
}
The columns map one to one:
| Column | Source | Why it earns a column |
|---|---|---|
| received_at | Apps Script new Date() |
Proves when the sheet got it, independent of broker clock |
| event_id | EA | Dedupe key if the EA retries |
| account | EA | Lets one sheet cover several challenge accounts |
| server_time | EA | Broker time, the clock your firm's rules use |
| event | EA | WARN (near-miss), BLOCK, or BREACH |
| rule | EA | daily_loss, max_drawdown, news_blackout, lot_cap |
| limit and value | EA | The numbers behind the decision |
| pct_of_limit | EA | Sortable measure of how close you came |
| action | EA | What the EA actually did |
Keep both timestamps. Broker server time is what matters for daily loss resets, and received_at tells you whether delivery lagged.
Step by step: from alert to row
- Create the sheet. Name a tab
eventsand put the column headers from the table in row 1. Open Extensions, then Apps Script, from inside this sheet so the script is bound to it. - Paste the receiver. Use the script below. It checks a shared token, ignores repeats of the saje event_id, and appends one row.
- Store the token. In Project Settings, add a script property named
TOKENwith a long random string. Put the same string in the EA's webhook settings. - Deploy. Choose Deploy, New deployment, type Web app. Execute as yourself, access set to Anyone. Copy the
/execURL. - Allow the URL in MT5. Under Tools, Options, Expert Advisors, tick Allow WebRequest for listed URL and add
https://script.google.com. MT5 blocks WebRequest calls to anything not on that list. - Send a test event. Have the EA fire a test alert, or POST the sample payload with curl, and confirm the row appears.
function doPost(e) {
const lock = LockService.getScriptLock();
lock.waitLock(10000);
try {
const body = JSON.parse(e.postData.contents);
const token = PropertiesService.getScriptProperties().getProperty('TOKEN');
if (body.token !== token) return ContentService.createTextOutput('denied');
const tab = (e.parameter && e.parameter.tab) || 'events';
const sh = SpreadsheetApp.getActive().getSheetByName(tab);
const seen = sh.getRange('B:B').createTextFinder(body.event_id)
.matchEntireCell(true).findNext();
if (seen) return ContentService.createTextOutput('dup');
sh.appendRow([
new Date(), body.event_id, body.account, body.server_time,
body.event, body.rule, body.limit, body.value,
body.pct_of_limit, body.action, body.symbol
]);
return ContentService.createTextOutput('ok');
} finally {
lock.releaseLock();
}
}
Apps Script web apps cannot read request headers, which is why the token travels in the body. Responses also come back through a redirect, so have the EA treat any HTTP 200 or 302 as delivered and ignore the body. The row is written before the redirect happens.
Add the human side with a short form
Logs tell you what the EA did. They do not tell you why you were pushing a position at 92% of your daily loss. That part you have to write down, and a form is the easiest way to make it happen right after a block.
Qwenvo is a completely free form builder that our team builds, and it fits here beacuse webhooks, hidden fields and custom domains are all free on every account. Make a three question form: what were you trying to do, what state were you in, what will you change. Add event_id and account as hidden fields, and link to the foorm with those values in the URL so the review attaches to the exact event. Then point the form's webhook at the same Apps Script URL with ?tab=reviews on the end, and the answers land in a second tab you can join to events on event_id.
One honest note: Qwenvo's one-click Google Sheets connection is still being rolled out, so today you use the webhook route shpwn here. The form to Google Sheets guide covers the options. Check the payload format in the webhooks help page and adjust the appendRow call for the reviews tab to match.
Keeping the sheet reliable
Most failures come from a small set of causes, so check these first.
- Nothing arrives. The URL is missing from the MT5 WebRequest allowlist. MT5 then returns an error code in the Experts tab instead of sending.
- Rows stop after you edit the script. Edits do not go live until you redeploy. Use Manage deployments and edit the existing one to a new version, so the URL stays the same.
- Duplicates. If the EA retries after a timeout, the same event arrives twice. The event_id check in the script handles it.
- Missing rows during a Google outage. Treat the sheet as a mirror. Enforcement should never wait on it, and the EA's own journal stays the fallback you backfill from.
- Slow trades. WebRequest is synchronous in MT5, so alerts should fire on events, never on every tick, with a short timeout.
- Wrong day in reports. Group by server_time, not received_at, when you total daily loss.
What to measure
Once rows accumulate, a few pivot tables carry most of the value. Count WARN events per rule per week to see which limit you keep approaching. Sort by pct_of_limit to find your closest calls. Filter BLOCK events by hour to see whether trouble clusters around the news blackout window or the end of the session. Compare the reviews tab against the same event_ids to see whether the days you wrote honest notes were followed by fewer warnings.
For a funded account, a clean log also works as a record that your rules ran and what they did on a given day, which is handy if you ever need to discuss an incident with your firm.
Alternatives to Apps Script
You do not have to write the receiver yourself. Any webhook trigger wprks, and Zapier, Make and n8n all accept one and can add a row to a sheet.
| Route | Good for | Trade-off |
|---|---|---|
| Apps Script web app | Full control, no extra service, token and dedupe in code | You maintain a small script |
| Zapier, Make or n8n webhook | No code, easy to branch to Telegram or email | One more service in the path, plus its plan limits |
| Qwenvo form webhook | Human review notes next to the machine log | Not a source for EA events, and the one-click Sheets connection is not live yet |
Questions teams ask
Can I send alerts to Telegram or email as well as the sheet?
Yes. Keep the sheet as the permanent record and let a second route handle live pings. In Zapier, Make or n8n, one webhook trigger can fan out to both. Either way, the sheet row should be written first.
Does this work for several challenge accounts at once?
Yes, because every row carries the account number. Run one EA per account, point them all at the same URL, and filter by account in the sheet. If you want separate sheets per account, deploy one script per sheet.
Is it safe to leave the web app open to Anyone?
The URL is unguessable but not secret forever, so the shared token matters. Keep it in script properties, rotate it if the EA settings ever leak, and never put account passwords in the payload.
How do we get this set up on our own limits?
The Prop-Firm Guardian EA is a $149 setup plus a $19 monthly care plan, installed and walked through in 3 days, configured to your firm's exact limits and breach alerts.
Next step
Create the events tab today, paste the script, and deploy it. Then fire one test POST with curl before you connect anything live, and check that the row, btoh timestamps and the dedupe all behave. Once that works, the EA side is only a URL and a token.
Prop-Firm Guardian EA
Third-party platform and model usage billed to you directly. No lock-in. Live in 3 days.