One handler for both: Sume run webhook payload equals the GET receipt

A Sume run webhook's payload is byte-identical to GET /v1/format-runs/{run_id}. Write one function for polling and webhook, and branch on outcome.

4 min readSume
All posts

If you poll a Sume Format run and also take a webhook for it, you can write the code that reads a finished run once. The run webhook envelope carries the whole run receipt in its payload field, and the docs say that receipt is byte-identical to data from GET /v1/format-runs/{run_id}. One handler serves both transports.

What is in the envelope

A delivery for the event format.run.terminal has event, request_id, run_id, object, status, outcome, created_at, payload and error. request_id equals the run id and is stable across retries, so use it to dedupe. created_at is the time Sume built that body, so use it to order deliveries.

status is binary: OK when the run completed, ERROR when it failed. outcome is the field to branch on, with the values ok, degraded and error. A completed run whose output could not be projected into your output_schema is OK with outcome degraded, an output of null and an output_error that gives the reason.

One function for both paths

Write a function that takes the receipt, the payload, and decides what to do. A poller calls it with data from the GET. A webhook handler calls it with payload from the verified body. The test suite then needs one set of fixtures, and a fix lands in both paths.

function handleReceipt(run, outcome) {
  if (outcome === "ok") return { ship: run.output, url: run.primary_output_url };
  if (outcome === "degraded") return { review: run.artifacts, why: run.output_error };
  return { failed: run.error };
}

export function fromPoll(body) {
  const run = body.data ?? body;
  const outcome = run.status === "completed" ? (run.output ? "ok" : "degraded") : "error";
  return handleReceipt(run, outcome);
}

export function fromWebhook(event) {
  if (event.payload === null) return { refetch: event.error?.result_url };
  return handleReceipt(event.payload, event.outcome);
}

Two cases that differ

The first is overflow. If the receipt is larger than 1 MiB, payload is null and error.code is payload_too_large, with a result_url to fetch the receipt. The handler above refetches in that case.

The second is outcome. A poll gives you no outcome field, so compute it from status and output as the sample does. Verify the signature of the delivery against the raw body before you trust any of it; the SDK function verifyWebhook is async and returns false instead of throwing.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume