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

  1. Use a private synthetic test spreadsheet and an authorised Google connection.
  2. Create the tab YOUR_TAB. Set A1 to event_id, B1 to status, and C1 to note for testing. Seed the records before sending an update. Use plain-text IDs, populated text cells and unique IDs in normal use.
  3. Set Universal Webhook to POST, Platform API Key authentication and From Flow response mode. Keep the sender’s key private.
  4. Use Search Row with Key Column event_id, Header Row 1, Case Sensitive Key Match enabled and Sheet Name YOUR_TAB. Configure Update Range to RAW and disable Include Object Headers.
  5. 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.
  6. 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
Source outputTarget inputMapping
Universal Webhook.request_dataValidation.dataRead .body; emit only verified, event_id and status on valid input.
Validation.data_outputValid router.verification_resultUse the top-level verified boolean.
Valid router.verifiedSearch Row.row_dataLookup event_id; this component does not write.
Valid router.verifiedCombine.dataRetain the incoming status and event_id.
Search Row.resultCombine.dataAdd found, key_value and row_number to the retained input.
Combine.data_outputLookup JSON Operations.dataRequire a boolean found; on a match, prepare row_number and values:[[status]].
Lookup.data_outputFound router.verification_resultverified is the lookup result; missing rows carry a response definition.
Found router.verifiedData to Message.dataTemplate: 'YOUR_TAB'!B{row_number}.
Data to Message.textUpdate Range.range_nameA Message supplies the exact status-cell range.
Found router.verifiedUpdate Range.valuesUse the values wrapper containing one status value.
Update Range.resultSuccess JSON Operations.dataRequire updatedCells == 1; project only {status:"updated"}.
Success.data_output200 response.body_dataHTTP 200 application/json.
Found router.rejected404 response.response_definition{status_code:404,body:{status:"not-found"}}; no write.
Valid router.rejected422 response.body_dataReturn 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.

Production results — 9 October 2026 MYT
CaseHTTP resultSheet effectVerdict
T1 — Valid existing row2007 -> 7 data rows. Only first matching status cell updated; other cells unchanged.PASS
T2a — Missing status4227 -> 7 data rows. All values unchanged; no write.PASS
T2b — Missing event_id4227 -> 7 data rows. All values unchanged; no write.PASS
T3a — Numeric status4227 -> 7 data rows. All values unchanged; no write.PASS
T3b — Numeric event_id4227 -> 7 data rows. All values unchanged; no write.PASS
T4 — Malformed JSON4227 -> 7 data rows. All values unchanged; no write.PASS
T5 — Missing API key4037 -> 7 data rows. All values unchanged; no write.PASS
T6 — Invalid API key4037 -> 7 data rows. All values unchanged; no write.PASS
T7 — Absent event_id4047 -> 7 data rows. All values unchanged; no write.PASS
T8a — Repeat identical update2007 -> 7 data rows. Repeated update; values unchanged.PASS
T8b — Change status again2007 -> 7 data rows. Only first matching status cell updated; other cells unchanged.PASS
T9a — Case mismatch4047 -> 7 data rows. All values unchanged; no write.PASS
T9b — Leading-space mismatch4047 -> 7 data rows. All values unchanged; no write.PASS
T9c — Leading-zero text ID2007 -> 7 data rows. Only first matching status cell updated; other cells unchanged.PASS
T10 — Extra field ignored2007 -> 7 data rows. Only first matching status cell updated; other cells unchanged.PASS
T11a — Null status4227 -> 7 data rows. All values unchanged; no write.PASS
T11b — Empty status4227 -> 7 data rows. All values unchanged; no write.PASS
T11c — Blank status4227 -> 7 data rows. All values unchanged; no write.PASS
T11d — Null event_id4227 -> 7 data rows. All values unchanged; no write.PASS
T11e — Empty event_id4227 -> 7 data rows. All values unchanged; no write.PASS
T11f — Blank event_id4227 -> 7 data rows. All values unchanged; no write.PASS
T12a — 100-character status2007 -> 7 data rows. Only first matching status cell updated; other cells unchanged.PASS
T12b — 101-character status4227 -> 7 data rows. All values unchanged; no write.PASS
T12c — 100-character seeded ID2007 -> 7 data rows. Only first matching status cell updated; other cells unchanged.PASS
T12d — 101-character ID4227 -> 7 data rows. All values unchanged; no write.PASS
T13a — Array body4227 -> 7 data rows. All values unchanged; no write.PASS
T13b — Null body4227 -> 7 data rows. All values unchanged; no write.PASS
T13c — String body4227 -> 7 data rows. All values unchanged; no write.PASS
T14 — Formula-like status with RAW2007 -> 7 data rows. Only first matching status cell updated; other cells unchanged.PASS
T15 — Whitespace preserved2007 -> 7 data rows. Only first matching status cell updated; other cells unchanged.PASS
T16 — Duplicate key updates first match only2007 -> 7 data rows. Only first matching status cell updated; other cells unchanged.PASS
T17a — Seeded whitespace ID2007 -> 7 data rows. Only first matching status cell updated; other cells unchanged.PASS
T17b — Untrimmed whitespace mismatch4047 -> 7 data rows. All values unchanged; no write.PASS
T18 — Unicode status boundary2007 -> 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, header event_id in 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.

Design estimates, not observed charges
PathNodes per request100 requests1,000 requests10,000 requests
Updated row111,10011,000110,000
Missing row88008,00080,000
Validation rejection44004,00040,000
Authentication rejection before graph0 expected0 expected0 expected0 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.