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
- Connect Claude Code, Cursor, or Codex to Sume with hosted MCP
Sume's hosted MCP server at mcp.sume.com/mcp lets coding agents generate images, video, audio, and avatars. Setup, OAuth scopes, and spend gates.
- Idempotency keys for AI video APIs: retry without paying twice
An idempotency key makes a retried create return the original run or job instead of a second paid one. How Sume's Idempotency-Key works on each API.
- Spend caps for unattended AI agents: how Sume bounds each run
An unattended agent has no one to approve spend, so Sume caps generation per run: required on Agent Completions, and up to $500 on Format runs.
- How Sume API keys work: scopes, auth headers, hosts, and rotation
A Sume API key is a workspace-scoped secret sent as Bearer or x-api-key, never both. Scopes are fixed at creation, and a key works only on its own host.
Written by Sume