Update a row’s status in Google Sheets
Receive an event ID and a status through a webhook, validate the input, find the existing Google Sheets row, and write its status cell. A missing ID takes a separate response path without a write. This template uses no AI model.
Status: production-tested on 9 October 2026 on AI Flowi Workflow 1.9.2 (34 of 34 cases passed)
By Ai Flowi · Published . Updated 9 October 2026. Production test date: 9 October 2026 (MYT).
Task
Accept an authenticated HTTP POST containing {event_id,status}. Search for an exact, case-sensitive event_id in a pre-existing row. The observed result was HTTP 200 with {"status":"updated"} after writing its status, or HTTP 404 with {"status":"not-found"} if lookup finds no row. The missing-row branch contains no write component.
Who it is for
Teams that keep event or job records in a spreadsheet and need a sender to mark an existing record as queued, in progress or complete. The sender supplies the status. This workflow accepts free-form status text; it does not infer a status or enforce business transitions.
Sample input
{"event_id":"demo-event-001","status":"complete"}
Both fields must be strings with 1–100 Unicode code points and at least one non-whitespace character. Whitespace is preserved. IDs are case sensitive: 001 differs from 1. Extra request fields are discarded during validation.
Setup
- Use a private synthetic test spreadsheet and an authorised Google connection.
- Create the tab
YOUR_TAB. Set A1 toevent_id, B1 tostatus, and C1 tonotefor testing. Seed the records before sending an update. Use plain-text IDs, populated text cells and unique IDs in normal use. - Set Universal Webhook to POST, Platform API Key authentication and From Flow response mode. Keep the sender’s key private.
- Use Search Row with Key Column
event_id, Header Row1, Case Sensitive Key Match enabled and Sheet NameYOUR_TAB. Configure Update Range toRAWand disable Include Object Headers. - Connect the nodes below. Keep JSON Operations’ selected operation and JQ fields visible. Set the Combine node’s JSON input to accept a list. Do not move, sort, insert or delete sheet rows during a request.
- Re-run the documented tests on the private tab. Check responses and the complete sheet after each request before using the workflow with real records.
Node connections and field mappings
The graph has 13 nodes: Universal Webhook; JSON Operations for validation; Verification Router for valid input; Search Row; JSON Operations with Combine; JSON Operations to evaluate lookup; Verification Router for found rows; Data to Message; Update Range; JSON Operations for success; and three Webhook Response nodes for 200, 404 and 422.
| Source output | Target input | Mapping |
|---|---|---|
Universal Webhook.request_data | Validation.data | Read .body; emit only verified, event_id and status on valid input. |
Validation.data_output | Valid router.verification_result | Use the top-level verified boolean. |
Valid router.verified | Search Row.row_data | Lookup event_id; this component does not write. |
Valid router.verified | Combine.data | Retain the incoming status and event_id. |
Search Row.result | Combine.data | Add found, key_value and row_number to the retained input. |
Combine.data_output | Lookup JSON Operations.data | Require a boolean found; on a match, prepare row_number and values:[[status]]. |
Lookup.data_output | Found router.verification_result | verified is the lookup result; missing rows carry a response definition. |
Found router.verified | Data to Message.data | Template: 'YOUR_TAB'!B{row_number}. |
Data to Message.text | Update Range.range_name | A Message supplies the exact status-cell range. |
Found router.verified | Update Range.values | Use the values wrapper containing one status value. |
Update Range.result | Success JSON Operations.data | Require updatedCells == 1; project only {status:"updated"}. |
Success.data_output | 200 response.body_data | HTTP 200 application/json. |
Found router.rejected | 404 response.response_definition | {status_code:404,body:{status:"not-found"}}; no write. |
Valid router.rejected | 422 response.body_data | Return the validation error; no lookup or write. |
The Combine node receives two JSON objects with distinct field names. The lookup projection verifies the match’s row number and key before routing. Search uses the event_id header; the write uses status column B. If you change the column order, update the range template too. Update Range changes one cell, leaving the event ID, notes and other cells outside that range untouched.
JSON Operations settings
Select JQ Expression for validation, lookup and success. Select Combine only on the two-input Combine node. Keep the selected fields visible.
Related: Write a scheduled status row to Google Sheets.
Validation query
def valid_text: if type == "string" then length >= 1 and length <= 100 and test("\\S") else false end;
.body as $b | if ($b|type) != "object" then
{verified:false,error:"Request body must be a JSON object with event_id and status."}
else [ (if ($b.event_id|valid_text) then empty else "event_id must be non-blank text between 1 and 100 characters" end),
(if ($b.status|valid_text) then empty else "status must be non-blank text between 1 and 100 characters" end) ] as $errors
| if ($errors|length) > 0 then {verified:false,error:"Validation failed",details:$errors}
else {verified:true,event_id:$b.event_id,status:$b.status} end end
Lookup query
if (.found|type) != "boolean" then error("Unexpected search result")
elif .found == false then {verified:false,status_code:404,body:{status:"not-found"}}
elif (.row_number|type) != "number" then error("Invalid matched row")
elif .row_number < 2 or (.row_number|floor) != .row_number or .key_value != .event_id then error("Invalid matched row")
else {verified:true,row_number:.row_number,values:[[.status]]} end
Success query
if .updatedCells == 1 then {status:"updated"} else error("Unexpected update result") end
Sanitized request and response examples
These response examples matched the synthetic production tests.
POST <production-webhook-url>
Content-Type: application/json
x-api-key: <private-platform-api-key>
{"event_id":"demo-event-001","status":"complete"}
Expected: HTTP 200
{"status":"updated"}
{"event_id":"demo-event-absent","status":"complete"}
Expected: HTTP 404; sheet unchanged
{"status":"not-found"}
{"event_id":"demo-event-001"}
Expected: HTTP 422; sheet unchanged
{"verified":false,"error":"Validation failed","details":["status must be non-blank text between 1 and 100 characters"]}
Validation and failure behaviour
Missing fields, nulls, wrong types, empty or whitespace-only strings and strings longer than 100 Unicode code points returned 422 in the production cases before lookup or write. Non-object bodies and malformed JSON returned 422 in the production cases with {"verified":false,"error":"Request body must be a JSON object with event_id and status."}. Missing or invalid platform keys returned 403; the exact authentication response body is platform-controlled.
Only a successful Search Row result with found:false produces not-found. Google access errors interrupt the workflow and may expose a raw platform error. No configured component catches those exceptions. An unexpected lookup shape or an update result other than one updated cell raises an error instead of claiming an update.
Test results
All 34 production HTTP cases passed with full-tab read-back. The sheet retained seven data rows throughout. Successful updates changed only the first matching status cell; lookup misses and rejected input left all values unchanged. An identical repeat left values unchanged. Temporary API key deletion was confirmed.
| Case | HTTP result | Sheet effect | Verdict |
|---|---|---|---|
| T1 — Valid existing row | 200 | 7 -> 7 data rows. Only first matching status cell updated; other cells unchanged. | PASS |
| T2a — Missing status | 422 | 7 -> 7 data rows. All values unchanged; no write. | PASS |
| T2b — Missing event_id | 422 | 7 -> 7 data rows. All values unchanged; no write. | PASS |
| T3a — Numeric status | 422 | 7 -> 7 data rows. All values unchanged; no write. | PASS |
| T3b — Numeric event_id | 422 | 7 -> 7 data rows. All values unchanged; no write. | PASS |
| T4 — Malformed JSON | 422 | 7 -> 7 data rows. All values unchanged; no write. | PASS |
| T5 — Missing API key | 403 | 7 -> 7 data rows. All values unchanged; no write. | PASS |
| T6 — Invalid API key | 403 | 7 -> 7 data rows. All values unchanged; no write. | PASS |
| T7 — Absent event_id | 404 | 7 -> 7 data rows. All values unchanged; no write. | PASS |
| T8a — Repeat identical update | 200 | 7 -> 7 data rows. Repeated update; values unchanged. | PASS |
| T8b — Change status again | 200 | 7 -> 7 data rows. Only first matching status cell updated; other cells unchanged. | PASS |
| T9a — Case mismatch | 404 | 7 -> 7 data rows. All values unchanged; no write. | PASS |
| T9b — Leading-space mismatch | 404 | 7 -> 7 data rows. All values unchanged; no write. | PASS |
| T9c — Leading-zero text ID | 200 | 7 -> 7 data rows. Only first matching status cell updated; other cells unchanged. | PASS |
| T10 — Extra field ignored | 200 | 7 -> 7 data rows. Only first matching status cell updated; other cells unchanged. | PASS |
| T11a — Null status | 422 | 7 -> 7 data rows. All values unchanged; no write. | PASS |
| T11b — Empty status | 422 | 7 -> 7 data rows. All values unchanged; no write. | PASS |
| T11c — Blank status | 422 | 7 -> 7 data rows. All values unchanged; no write. | PASS |
| T11d — Null event_id | 422 | 7 -> 7 data rows. All values unchanged; no write. | PASS |
| T11e — Empty event_id | 422 | 7 -> 7 data rows. All values unchanged; no write. | PASS |
| T11f — Blank event_id | 422 | 7 -> 7 data rows. All values unchanged; no write. | PASS |
| T12a — 100-character status | 200 | 7 -> 7 data rows. Only first matching status cell updated; other cells unchanged. | PASS |
| T12b — 101-character status | 422 | 7 -> 7 data rows. All values unchanged; no write. | PASS |
| T12c — 100-character seeded ID | 200 | 7 -> 7 data rows. Only first matching status cell updated; other cells unchanged. | PASS |
| T12d — 101-character ID | 422 | 7 -> 7 data rows. All values unchanged; no write. | PASS |
| T13a — Array body | 422 | 7 -> 7 data rows. All values unchanged; no write. | PASS |
| T13b — Null body | 422 | 7 -> 7 data rows. All values unchanged; no write. | PASS |
| T13c — String body | 422 | 7 -> 7 data rows. All values unchanged; no write. | PASS |
| T14 — Formula-like status with RAW | 200 | 7 -> 7 data rows. Only first matching status cell updated; other cells unchanged. | PASS |
| T15 — Whitespace preserved | 200 | 7 -> 7 data rows. Only first matching status cell updated; other cells unchanged. | PASS |
| T16 — Duplicate key updates first match only | 200 | 7 -> 7 data rows. Only first matching status cell updated; other cells unchanged. | PASS |
| T17a — Seeded whitespace ID | 200 | 7 -> 7 data rows. Only first matching status cell updated; other cells unchanged. | PASS |
| T17b — Untrimmed whitespace mismatch | 404 | 7 -> 7 data rows. All values unchanged; no write. | PASS |
| T18 — Unicode status boundary | 200 | 7 -> 7 data rows. Only first matching status cell updated; other cells unchanged. | PASS |
Tested platform version: AI Flowi Workflow 1.9.2. Test date: 9 October 2026 (MYT). Observed node runs per path: Not measured. Import and re-test result: Not performed in supplied evidence.
Download
Workflow import download: Not available; imported-copy test not performed. Add a public import file only after a sanitized export and imported-copy test. The node configuration is documented above.
Required configuration
- Your authorised Google connection and Spreadsheet ID on both Sheets components.
- Tab
YOUR_TAB, headerevent_idin row 1 and status in column B. - Pre-existing text IDs and a stable row layout during execution.
- The production webhook URL and sender-side Platform API Key.
- Two-input Combine configuration, lookup routing and separate 200, 404 and 422 responses.
Tested version and date
Tested version: AI Flowi Workflow 1.9.2. Tested date: 9 October 2026 (MYT). The supplied evidence covers this production graph and synthetic fixtures; it does not establish imported-copy results.
Usage and cost assumptions
Plans: Go from USD8/month billed yearly (USD10 monthly), Plus from USD16 (USD20 monthly), Pro 100 from USD80 (USD100 monthly), Pro 200 from USD160 (USD200 monthly) and Pro 500 from USD400 (USD500 monthly), with 5,000 to 250,000 Flowi Tokens/month and top-ups; 1 token per node run; third-party API and model usage is not included.
This design uses no AI or LLM component and no third-party paid API. A Google account, sheet access and service quotas apply. The following estimates assume one run per visited node, one selected router branch, no retries and no read-back verification flow. Production accounting remains pending.
| Path | Nodes per request | 100 requests | 1,000 requests | 10,000 requests |
|---|---|---|---|---|
| Updated row | 11 | 1,100 | 11,000 | 110,000 |
| Missing row | 8 | 800 | 8,000 | 80,000 |
| Validation rejection | 4 | 400 | 4,000 | 40,000 |
| Authentication rejection before graph | 0 expected | 0 expected | 0 expected | 0 expected |
Each verification read adds one node run. Repeated deliveries execute the workflow again; an identical status still causes a write. Failed attempts and retries may consume additional tokens. Third-party API/model usage is not included in the plan.
Limitations
- Lookup and write are separate calls. Sorting, row insertion or deletion between them can make the stored row number point to a different row. Keep the layout stable; use a transactional data store if concurrent structural changes are required.
- Search selects the first matching row. Duplicate IDs remain; normal use should keep IDs unique.
- Concurrent status writes can overwrite each other. There is no conflict detection or status-transition policy.
- The status column must remain B. RAW stores formula-like status text literally; it does not execute that text as a formula.
- Google failures do not have a sanitized in-flow response. A write may succeed even if its response is lost; inspect the sheet before retrying an uncertain delivery.
- The 34 production cases passed; node accounting and the sanitized imported-copy run remain unverified. Read-back compares values, not sheet formatting or permissions.
Next steps
Re-run every case on your own private configuration and inspect complete row contents. Review concurrent row changes and raw Google failures. Add a sanitized import download after an imported-copy test. See Universal Webhook, Google connection setup and common errors. For webhook and Google connection setup background, see the Webhook to Google Sheets tutorial.
Related: Look up a row in Google Sheets by email.