Run webhook payload is null: payload_too_large means fetch result_url

Sume cannot send a receipt over 1 MiB inline. The webhook arrives with payload null and error.code payload_too_large. The run did not fail. Fetch result_url.

5 min readSume
All posts

A payload: null webhook with error.code of payload_too_large does not mean the run failed. Sume's run webhook docs say a receipt over 1 MiB (1,048,576 bytes) cannot be delivered inline, so the envelope goes out with a null payload and an error that carries a result_url. The top-level status still reports the real outcome, so a succeeded run that was too big to ship is still a success (Run webhooks).

What the envelope looks like

The documented body for the oversized case has status: OK, outcome: ok, payload: null, and an error object with code, message and result_url. The message tells you to fetch the receipt from result_url instead.

This is easy to mishandle. A handler that treats any non-null error as a failure will mark a successful run failed. A handler that dereferences payload without a check will crash on exactly the large runs, which are often the expensive ones.

Webhook envelope cases, read 2026-10-05
Casestatuspayloaderror
Normal successOKFull receiptnull
Run failedERRORFull receiptcode and message
Oversized receiptReal outcome, e.g. OKnullpayload_too_large with result_url
Canceled runNo webhookNoneNone

A handler that fetches when it must

The function below returns the receipt from either the inline payload or the result_url. The fetch is injected so it can run offline with a stub. In production, make the fetch a GET with your API key; the run is not complete until you hold the receipt.

def receipt_for(event: dict, fetch) -> dict:
    if event.get("payload") is not None:
        return event["payload"]
    err = event.get("error") or {}
    if err.get("code") == "payload_too_large" and err.get("result_url"):
        return fetch(err["result_url"])
    raise ValueError("no payload and no result_url")

if __name__ == "__main__":
    big = {"status": "OK", "payload": None,
           "error": {"code": "payload_too_large",
                     "result_url": "https://api.sume.com/v1/x/result"}}
    print(receipt_for(big, lambda url: {"fetched": url}))
    print(receipt_for({"payload": {"id": "r1"}}, None))

Why receipts get large

A receipt carries output, artifacts and usage. A run that makes many clips, or a structured output with long text, can grow past 1 MiB. Nothing about that size means the run misbehaved. It is a delivery limit, not a quality signal.

If you control the run, you can keep receipts small by asking for a tight output_schema and a primary_output_key, so the headline result is easy to find. Even so, always handle the null case.

Where this goes wrong in practice

The failure shows up as a quiet bug. A receiver written against a small test run works for weeks, then a run with many generated files produces a large receipt, the handler throws on a null payload, and the endpoint returns a 500. Sume then retries the same delivery up to 10 times, each one failing the same way, and the delivery ends as exhausted even though the run succeeded.

Return a 2xx once you have stored the event, including the oversized case. Then fetch the receipt from result_url in a separate job, so a slow download never eats the 10-second attempt timeout. Log the payload_too_large code as an informational event, not an error, so your alerts stay meaningful.

Checklist

Add a test for the oversized case before you go live.

  • Never assume payload is non-null.
  • Do not treat error as failure when status is OK.
  • Fetch result_url with your API key, then continue as normal.
  • Still dedupe on the envelope request_id.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume