DEV Community

mumuzhang
mumuzhang

Posted on

I Got Tired of Manually Logging Invoices, So I Wired Webhooks Straight to a Google Sheet

The problem

I kept hitting the same friction: every time a payment provider fired an event, I rebuilt the same automation from scratch. Copying a previous flow helped for about an hour, then the small differences took over — different payload shapes, different failure modes, different places to look when nothing arrived. So I built a template that captures an invoice webhook, verifies the signature, drops duplicates, and writes a row to Google Sheets.

How it works

  1. Webhook — the only entry point; every execution starts from a request you can replay.
  2. HMAC verify — checks the signature before any body is trusted. Runs before parsing on purpose.
  3. Idempotency check — drops events whose id was already processed.
  4. Append to Sheets — writes the normalized record as one consistent row.
  5. Error branch — collects failures so they surface instead of disappearing.

The order matters: verifying before parsing means a malformed or hostile body never reaches code that acts on it. The idempotency guard is the piece I used to skip, and it's the piece that actually saves time.

A worked example

POST to the webhook:

{
  "id": "inv_001",
  "customer": "Acme",
  "amount": 4900,
  "currency": "USD",
  "x-signature": "sha256=..."
}
Enter fullscreen mode Exit fullscreen mode

After HMAC passes and the id is new, one row lands in the Invoices sheet: inv_001 | Acme | 4900 | USD.

What broke

The first version passed every happy-path test and then lost invoices in production for two reasons that only show up under load. First, I treated the HMAC check as optional on "internal" traffic — it isn't, because the moment someone copies the webhook URL into a second service, "internal" stops being true. Second, the error branch wrote to a log nobody opened. An error branch that only appends to a file is not an error branch; it has to end somewhere a human looks.

What I'd do differently

Write the idempotency key format down before writing code. Retrofitting a key scheme onto events already in flight is the least fun migration. And I'd make the error branch post to a channel from day one, not "later when I have time" — later never comes, and that's exactly when invoices go missing.

What it actually does

Captures an inbound invoice webhook, authenticates it with HMAC, drops duplicates via an idempotency guard, then appends a structured row to Google Sheets. Services: any HTTP endpoint that can POST JSON; delivery target is Google Sheets (Slack or Notion are easy to swap in). Extend it with rate limiting, retry queues, or a second destination in parallel.

Setup

  1. In n8n open Workflows → Import from File and select workflow.json.
  2. Open the HMAC verify node and set HMAC to your shared secret.
  3. Open the Append row to Google Sheets node and set the spreadsheet and sheet.
  4. Activate the workflow and copy the production webhook URL.

Key takeaways

  • One entry point makes the whole pipeline testable — replay the request.
  • Verify before parse. Always.
  • An error branch that ends in a log file is a silent failure, not a safeguard.

Try it

Import the workflow, set your secret and sheet, and fire a test event. If a duplicate id comes in, nothing double-writes — that's the guard doing its job.

Top comments (1)

Collapse
 
fun_fun_19313eced54af3c68 profile image
FUN FUN •

One gotcha worth adding to the HMAC step: most providers sign the raw request bytes, so in n8n you'll want the Webhook node's "Raw Body" option turned on. If the signature is checked against JSON that was already parsed and re-serialized, key order or whitespace can change and perfectly valid events start failing verification. Also curious where you keep the processed ids for the idempotency check: a separate Sheet tab, or n8n's workflow static data?