Check a webhook URL before you give it to Sume: https and public
Sume rejects localhost, private-network and non-HTTPS webhook URLs. A Python preflight that catches all three, and the 2048-character limit, before you submit.

Before you pass webhook_url on a submit, check that it is HTTPS, not localhost, not longer than 2048 characters, and that its hostname resolves to a public address. Sume rejects localhost, private-network and non-HTTPS URLs with 400 invalid_request, so a local check turns a failed paid-path call into a one-line message in your own tool. The check below uses only the standard library.
What Sume says about the URL
The 2048-character limit is stated for the Format run field. The check applies it to every URL, which is a safe upper bound.
| Rule | Where it is stated |
|---|---|
| Public HTTPS URLs only | Job webhooks and run webhooks |
| localhost, private-network and non-HTTPS URLs are rejected | Job webhooks and run webhooks |
| Up to 2048 characters | communication.webhook_url on run endpoints |
| Redirects are not followed | Delivery behavior |
| Passing webhook_url or callback_url without a mode selects webhook mode | Communication modes |
The preflight
The function returns None for a good URL and a reason string for a bad one. It resolves the hostname and tests every address, so a name that points at a private range fails even though the text looks public. Sume does its own check on its side, and this one only saves you a round trip.
import ipaddress, socket
from urllib.parse import urlparse
def check_webhook_url(url):
u = urlparse(url)
if u.scheme != "https" or not u.hostname:
return "must be an https URL"
if len(url) > 2048:
return "longer than 2048 characters"
if u.hostname == "localhost" or u.hostname.endswith(".localhost"):
return "localhost is rejected"
try:
infos = socket.getaddrinfo(u.hostname, u.port or 443, type=socket.SOCK_STREAM)
except socket.gaierror:
return "hostname does not resolve"
for info in infos:
ip = ipaddress.ip_address(info[4][0])
if ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_reserved:
return f"resolves to a non-public address ({ip})"
return None
if __name__ == "__main__":
for url in ("http://example.com/h", "https://localhost/h", "https://127.0.0.1/h", "https://example.com/hooks/sume"):
print(url, "->", check_webhook_url(url) or "ok")Limits of a local check
Keep the preflight in the same module that builds the submit body, so no code path can send an unchecked URL.
- DNS can change between your check and Sume's delivery. Treat the check as a lint, not a guarantee.
- A URL that answers 301 or 302 counts as a failed delivery, because Sume does not follow redirects. Register the final URL.
- During development, use a tunnel with a public HTTPS address, and run POST /v1/webhooks/test-deliveries before the first paid job.
Where the 400 shows up
If you skip the preflight, the failure is a 400 invalid_request on the create call, with a request_id in the error envelope. No job starts, so there is nothing to clean up, but a batch loop that does not stop on the first 400 will repeat the same mistake for every item. Run the check once at start-up for a fixed URL, or once per item if each customer supplies their own.
Sources
Related posts
More in Developers
- Check ad video length for Pinterest, LinkedIn and Google in Python
A short Python check of a video-inspect probe against Pinterest, LinkedIn and Google Video action length limits read on 2026-10-08, before you upload an ad.
- Check probe.has_audio before captions to avoid caption_no_speech
A silent clip fails Sume speech captions with caption_no_speech. Run video_inspect with frames false, read probe.has_audio, then pick STT captions or text cues.
- Check Sume webhook signatures in Python with hmac.compare_digest
A Python verifier for the sume-v1 header: raw bytes, constant-time compare, 300 s replay window, empty-secret refusal, and a Flask route that dedupes on job_id.
- Claude Code hook: exit 2 or a JSON deny for a paid Sume call?
Exit code 2 blocks and cannot be overridden by JSON; exit 0 with permissionDecision deny carries a reason. A runnable Python guard for Sume's paid tools.
Written by Sume