Sume run webhook 3xx redirect: a failed attempt, not a delivery

Sume does not follow redirects on run webhooks, so a trailing-slash 301 fails every attempt. How to find it with a no-follow probe and register the final URL.

5 min readSume
All posts

If your webhook URL answers with a redirect, Sume counts it as a failed delivery. The run webhooks page is blunt about it: redirects are not followed, and a 3xx is a failed attempt. The common cause is not an intentional redirect but a framework adding or removing a trailing slash, or a proxy upgrading HTTP to HTTPS, in front of a handler that works perfectly in your own tests. The fix is to register the final URL, and to test with a client that does not follow redirects.

Where the accidental redirects come from

Your own tests usually pass because curl with -L, a browser, or a test client follows the redirect. Production deliveries do not. Typical sources:

  • A framework that canonicalises /hooks/sume to /hooks/sume/ (or the reverse) with a 301 or 308 on a POST.
  • A CDN or load balancer rule that sends http:// to https://. Run webhook URLs must be HTTPS anyway, but a stale registered URL with http is rejected up front, while a redirecting www host is not.
  • A domain move: the old host returns 301 to the new host and nobody updated the Format run's communication.webhook_url.
  • An auth gateway that redirects unauthenticated requests to a login page.

Probe without following

The script starts a local server that behaves like a trailing-slash framework and probes both spellings with http.client, which never follows redirects, the same behaviour as the delivery worker. The first call shows a 301 and a verdict of failed attempt; the second shows the 204 of the canonical path. Swap the local server for your real URL and a signed test body to check a staging endpoint. It uses only the standard library.

import http.client, threading
from http.server import BaseHTTPRequestHandler, HTTPServer

class H(BaseHTTPRequestHandler):
    def do_POST(self):
        self.rfile.read(int(self.headers.get("content-length", 0)))
        if self.path == "/hooks/sume":          # no trailing slash
            self.send_response(301)
            self.send_header("location", "/hooks/sume/")
        else:
            self.send_response(204)
        self.end_headers()
    def log_message(self, *a): pass

srv = HTTPServer(("127.0.0.1", 0), H)
threading.Thread(target=srv.serve_forever, daemon=True).start()

def probe(path):
    c = http.client.HTTPConnection("127.0.0.1", srv.server_port)
    c.request("POST", path, body=b"{}")
    r = c.getresponse()   # http.client does not follow redirects
    verdict = "delivered" if 200 <= r.status < 300 else "FAILED attempt"
    return r.status, r.getheader("location"), verdict

print(probe("/hooks/sume"))
print(probe("/hooks/sume/"))

What Sume does around it

The URL is re-validated as a public HTTPS URL at delivery time, not only when you submit. Each failed attempt is retried on the schedule in the docs, 10 attempts with a 10-second timeout, and the receipt's webhook_delivery object tells you what happened: its state (pending, retrying, delivered, failed, exhausted) and last_status_code. A last_status_code of 301 or 308 on a receipt is the signature of this bug.

Fixing the URL does not change runs that already carry the bad one: a redeliver re-sends to the URL the run was created with. For those runs, read the receipt from the status or result URL and act on it directly; new runs should be created with the corrected communication.webhook_url.

read 2026-10-03
Your endpoint answersSume treats it asFix
Redirect response (3xx)Failed attempt, retriedRegister the final URL
200 to 299DeliveredNothing
401 from your verifierFailed attemptCheck secret and raw body
Timeout over 10 sFailed attemptAcknowledge first, then work

Make it a deploy check

Add the no-follow probe to the pipeline that deploys your receiver. Any 3xx on the registered path fails the build, and the check takes one request. Pair it with a periodic read of webhook_delivery.last_status_code on recent runs, so a proxy change made by someone else does not stay invisible until a customer asks where their video is.

Reading the receipt

The receipt's webhook_delivery block is the quickest diagnostic: status says pending, retrying, delivered, failed or exhausted, last_status_code shows what your endpoint answered, manual_redeliveries counts the times someone re-sent it, and signing_secret_fingerprint identifies the secret used. A run stuck in retrying with a 3xx status code is this bug, while a 401 is a signature problem and a timeout is your handler being slow. Delivery outcomes never change the run itself; the receipt remains the truth.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume