Webhook URL rejected as invalid? Sume's webhook URL rules
Sume answers 400 invalid_request when a webhook URL is not public HTTPS. The rules for scheme, host, port, and credentials, and the check at delivery.

Sume rejects a webhook URL with 400 invalid_request when it is not a public HTTPS URL. Plain http://, localhost, private-network addresses, and credentials in the URL are refused on every job and run, and current code also refuses a non-default port such as :8443. The check runs before anything is created, so fix the URL and submit again.
The documented rules come from Run webhooks, Runs and results, and Webhooks, read 2026-09-27. Rules marked as current code are read from the API source; the docs do not list them. Failed or missing deliveries after a URL was accepted are covered in Sume webhook not received?
Which webhook URLs does Sume refuse?
In current code one check covers webhook_url and its alias callback_url on generation jobs, including callback_url on POST /v1/videos, communication.webhook_url on Format, Action, and Agent Completion runs, and Send test. For hosts written as IP addresses, current code also refuses link-local addresses such as 169.254.169.254, carrier-grade NAT, multicast, and the IPv6 loopback, unique-local, and link-local ranges.
| Webhook URL | Result | Rule from |
|---|---|---|
http://hooks.example.com/sume | 400: not HTTPS | Docs |
https://localhost/sume, https://127.0.0.1/sume | 400: localhost | Docs |
https://10.0.0.5/sume, https://192.168.1.20/sume | 400: private network | Docs |
https://user:pass@hooks.example.com/sume | 400: credentials in the URL | Docs (runs), current code (jobs) |
| A URL longer than 2048 characters | 400 | Docs |
https://hooks.example.com:8443/sume | 400: a port other than the default 443 | Current code |
https://api.default.svc.cluster.local/sume, https://host.docker.internal/sume | 400: a name ending in .local, .internal, .test, or .localhost | Current code |
https://hooks.example.com/sume | Accepted | Docs |
What does the 400 response look like?
Every refusal is 400 invalid_request, but in current code the wording depends on the surface. Run creates and Send test name the field in the message and in details.field. Generation-job submits answer Invalid request body. and put the reason in details.errors[]: a path naming the field, and the message webhook_url must be a valid public HTTPS URL whichever field you used. Both are validation errors with retryable: false and next_action: fix_input, so sending the same URL again fails the same way. A run create's error, trimmed to the fields the docs show:
{
"error": {
"code": "invalid_request",
"message": "webhook_url must be a public HTTPS URL without credentials or an explicit port.",
"request_id": "req_…",
"details": { "field": "webhook_url" }
}
}Why was a URL that works in my browser refused?
Reachable from your browser is not the same as allowed. Reasons to check:
- It has a port.
https://staging.example.com:8443/hooksmay open in a browser, but current code refuses any port other than the default 443 and delivers on port 443. Serve the handler on the default HTTPS port. - It is an internal name, such as
api.default.svc.cluster.localorhost.docker.internal. Current code refuses names ending in.local,.internal,.test, or.localhost. - It uses plain HTTP, or puts a user and password in the URL. Sume signs its deliveries with HMAC-SHA256, so verify the signature instead; Signed webhooks for video runs shows how.
- It is your own machine. The POST comes from Sume, so for local work put a public HTTPS tunnel in front of the handler, as in Test Sume webhooks locally.
- The body disagrees with itself:
webhook_urlandcallback_urlwith different values, or, on a job submit,mode: "webhook"with no URL. Both are400 invalid_requesttoo.
Is the URL checked again when Sume delivers?
Yes. The docs say the URL is re-validated as a public HTTPS URL at delivery time, and redirects are not followed: a 3xx is a failed attempt, so register the final URL. Current code also resolves the hostname at delivery, refuses the delivery if any address it resolves to is non-public, and connects only to the addresses it checked.
So a public-looking name that resolves to an internal address passes the submit and fails at delivery. The job or run keeps its real outcome, its webhook_delivery shows failed with the reason in last_error, and current code does not retry a blocked target. A name that does not resolve at all is treated as a transient failure and retried. Redeliver never sends to a different URL, so a corrected URL needs a new job or run.
How do I check a URL before I submit real work?
Use Send test, the control on /dashboard/webhooks or POST /v1/webhooks/test-deliveries with account:write; it never creates a job or run. It applies the same URL rules, so it answers 400 invalid_request for the same URLs. In current code it also runs the delivery-time DNS check: a name that resolves to a non-public address comes back with status_code: null and the reason in error. Sume webhook not received? covers the rest of its response.
Sources
Related posts
More in Developers
- 400 Bad Request invalid JSON: causes and how to fix it
A 400 Bad Request for invalid JSON means your body did not parse. Check shell quoting, single quotes, trailing commas, and unserialized objects.
- 401 vs 403 vs 404: what each API error tells you
401 means your credentials are missing or invalid, 403 means they aren't enough, and 404 means not found, or hidden from you. How to fix each one.
- 429 vs 503: rate limit or server overload?
429 means you sent too many requests in a given time; 503 means the server can't handle requests right now. Both can carry Retry-After. What to do.
- 504 Gateway Timeout from an API: did my request go through?
A 504 from an API means a proxy stopped waiting for the server. Your request may still be running, so check before you resend. How to avoid 504s.
Written by Sume