Firestore create() with job_id as document id for Sume webhooks

Name the Firestore document after the Sume job_id and call create(). ALREADY_EXISTS marks a retry. Node Admin SDK sample, plus a claim state for crashes.

5 min readSume
All posts

Use the Sume job_id as the Firestore document id and write with create(), not set(). create() fails with ALREADY_EXISTS (gRPC code 6) when the document exists, so the first delivery succeeds and every retry is rejected by the database itself. Return 2xx in both cases, and start follow-up work only when the create succeeded.

That matches the rule in Sume's webhook docs: job_id is the idempotency key. Delivery makes up to 10 attempts, and Redeliver re-sends the real terminal event on request, so duplicates are normal and not an error.

The write

Job ids look like job_..., which are valid document ids. The sample stores the event as a JSON string so that nested payload.artifacts fields do not turn into queryable noise, and it records a status you can index.

import { initializeApp } from "firebase-admin/app";
import { getFirestore } from "firebase-admin/firestore";
initializeApp();
const jobs = getFirestore().collection("sumeJobs");

export async function firstDelivery(event) {
  if (!event.job_id) return false; // webhook.test has no job_id
  try {
    await jobs.doc(event.job_id).create({
      event: event.event,
      status: event.status,
      body: JSON.stringify(event),
      state: "claimed",
      receivedAt: new Date(),
    });
    return true;
  } catch (err) {
    if (err.code === 6) return false; // ALREADY_EXISTS: retry or Redeliver
    throw err;
  }
}

export const markDone = (jobId) => jobs.doc(jobId).update({ state: "done" });

What each branch returns to Sume

The document is the lock, so the order of operations decides what survives a crash. Create first, respond second, work third, and flip state last.

Receiver outcomes and the HTTP answer (Sume docs, read 2026-10-04)
SituationfirstDelivery()HTTP answer
New job_idtrue204 after the create succeeds
Retry of a delivered eventfalse204, no new work
Redeliver of a finished jobfalse204, no new work
Firestore unavailablethrows500 so Sume retries
Bad or missing signaturenot called401, before any write

Recover from crashes and missed deliveries

A webhook can fail ten times while the job still reached its real terminal state, and the jobs docs say to keep status_url polling available for that case. A scheduled function that lists claimed documents older than a few minutes, and a second one that polls jobs you expected but never saw, covers both crash and missed-delivery cases.

Failed and canceled jobs deliver on the same route with status: "ERROR" and an error object. They create a document like any other, so a failed render shows up in your data instead of vanishing.

Caveats

  • Verify the signature on the raw body before the create() call. A rejected body must not leave a document behind.
  • A document id cannot contain a slash. Sume job ids do not, but never build an id from user input.
  • Each attempt has a 10 second budget at Sume. Keep the handler to verify, create and respond.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume