run_task or run_workflow, the API returns immediately with a run ID, but the actual execution happens in the background and can take variable time.
Instead of polling the get_runs endpoint, you can use Webhooks to get notified when they finish. Webhooks fire when a run reaches a terminal status: completed, failed, terminated, timed_out, or canceled. A workflow with a retry policy sends one webhook after the final attempt by default.
This page covers setting them up, explains payload structure, signature verification, and handling delivery failures.
Step 1: Set webhook URL
For tasks
For agents
Set a default webhook when creating the agent, or override it per-run:When creating an agent, use
webhook_callback_url inside json_definition; this sets the default for all runs. When running an agent, use webhook_url at the top level to override for that specific run.Workflow retries and webhooks
Addretry_policy to a workflow definition to retry selected terminal outcomes. Skyvern keeps the same workflow_run_id and records each execution in attempts[].
final_only is the default. Skyvern sends one webhook after the last attempt. Set webhook_on_retry to every_attempt to receive a webhook after each attempt. Each payload includes the attempt number and retry state. Every attempt shares the same run_id. Use run_id, attempt, and retry_pending together as the idempotency key, not run_id alone, or your receiver discards the final attempt as a duplicate of the first. A run that is canceled during its retry delay keeps the terminal status of its last attempt, for example failed, and sends a second webhook for the same attempt: the first payload has retry_pending: true, the second has retry_pending: false and the same status. A receiver that keys on run_id and attempt alone discards that final payload.
Webhook delivery is recorded after delivery. If a worker crashes in that window, Temporal or OSS recovery waits for the bounded delivery grace period and retries while the timestamp is NULL, so endpoints must tolerate duplicates in that crash window. Runs without a webhook URL record completion as “nothing to deliver” and are not retried forever.
An owner stops accepting its lease 60 seconds before recovery may take it over, leaving that margin as an intentional process-pause window before the next owner proceeds.
If a worker pauses after the final webhook step starts, recovery may deliver that webhook again. Webhook consumers should tolerate duplicates.
The final workflow hook runs once per logical run under the same lease contract, except that a process paused across a takeover may observe a second call if it resumes between the fresh ownership check and the hook call.
When
retry_pending is true, the run can still change its final result. Wait for retry_pending: false before processing the run as final.
Step 2: Understand the payload
Skyvern sends a JSON payload with run results. Here’s a real example from a completed task: Webhook Payload:Optional: Verify webhook signatures
Skyvern signs every webhook and TOTP request with your API key using HMAC-SHA256, so you can verify the request actually came from Skyvern before acting on it. Headers sent with every request:x-skyvern-signature: HMAC-SHA256 signature of the payload, hex-encodedx-skyvern-timestamp: Unix timestamp when the request was sentContent-Type: application/json
Use constant-time comparison to prevent timing attacks:
- Python:
hmac.compare_digest() - TypeScript:
crypto.timingSafeEqual() - Go:
hmac.Equal()
== or ===) for signature comparison as they are vulnerable to timing attacks.Handling webhook failures
Task execution and webhook delivery are independent; a task can succeed while webhook delivery fails. When this happens, the run showsstatus: "failed" even though your data was extracted successfully.
Webhook delivery can fail due to network issues, server errors, or misconfigured URLs.
When this happens, the run is marked as failed and the error is recorded in the failure_reason field. Check it by calling get_run after the run terminates:
failure_reason field contains the specific error message, for example:
Even when webhook delivery fails, the task’s
output field may still contain extracted data if the browser automation completed successfully before the webhook attempt.- Server unreachable: Your server is down, behind a firewall, or the URL is incorrect. Verify the URL is publicly accessible (not
localhost) and check your server logs for incoming requests. - Timeout: Skyvern waits 10 seconds for a response. If your server takes longer, the delivery is marked as failed even if processing eventually succeeds. Return
200 OKimmediately and process the payload in a background job. - Server returns an error: Your endpoint received the payload but responded with a non-2xx status code (e.g., 500). Check your server logs to identify the issue.
- Signature validation fails: If your verification logic rejects the request, make sure you’re validating against the raw request body, not parsed-and-re-serialized JSON (re-serializing changes the byte representation). Also verify you’re using the same API key that created the run.
get_run to check if the run completed and retrieve the data directly.
Replaying webhooks
Once you’ve identified and fixed the issue, you can replay the webhook usingretry_run_webhook.
Workflow retry policies retry workflow execution, not webhook delivery. Webhook delivery remains a separate operation. You must explicitly call
retry_run_webhook after fixing a delivery issue. The endpoint returns HTTP 409 Conflict while a workflow retry is pending.webhook_url to send the payload to a new endpoint; useful if the original URL was misconfigured.
Next steps
Error Handling
Handle failures and map custom error codes
Reliability Tips
Write robust prompts and add validation blocks

