Webhooks#
Opt-in push notifications. Configure webhook endpoints on a project in the dashboard, then submit normally with an API key scoped to that project. The platform POSTs results to the project's active webhooks when executions complete.
How it works#
- You create a webhook endpoint on a project in the dashboard
- You submit code with an API key scoped to that project
- Your code executes in the sandbox
- The platform POSTs the result to active project webhooks with HMAC-SHA256 signature headers
- You verify the signature and process the result
Example#
# 1. Add https://your-app.com/hooks/result to the project in the dashboard.
# 2. Submit with a key scoped to that project.
curl -X POST "https://api.rustbox.sh/api/submit" \
-H "Content-Type: application/json" \
-H "X-API-Key: rb_live_your_project_key_here" \
-d '{"language": "python", "code": "print(42)"}'
Webhook payload#
When execution completes, the platform POSTs the full result to your URL:
{
"id": "a0928c83-0a1d-4f5e-b724-7ecad619538f",
"schema_version": "2.0",
"language": "python",
"profile": "judge",
"status": "completed",
"timestamps": {
"created_at": "2026-04-02T12:00:00.000Z",
"started_at": "2026-04-02T12:00:00.001Z",
"completed_at": "2026-04-02T12:00:00.026Z"
},
"result": {
"verdict": "AC",
"cause": "normal_exit",
"actor": "runtime",
"exit_code": 0,
"signal": null,
"error_message": null
},
"output": {
"stdout": "42\n",
"stderr": "",
"integrity": "complete"
},
"metrics": {
"cpu_time_secs": 0.009,
"wall_time_secs": 0.025,
"memory_peak_bytes": 3858432,
"cpu_wall_ratio": 0.36,
"divergence": "cpu_bound"
},
"evidence": {
"isolation": {
"mode": "strict",
"controls_applied": ["pid_namespace", "mount_namespace", "network_namespace", "memory_limit", "process_limit", "no_new_privileges"],
"controls_missing": [],
"proc_policy": "hardened",
"sys_policy": "builtin-deny",
"pidfd_mode": "native",
"reason": "All configured controls applied"
},
"limits": {
"rlimits": {
"cpu_secs": { "soft": 4, "hard": 4 },
"as_bytes": { "soft": 1073741824, "hard": 1073741824 },
"nofile": { "soft": 32, "hard": 32 },
"nproc": { "soft": 10, "hard": 10 }
},
"cgroup_backend": "cgroup_v2",
"cgroup": {
"memory_limit_bytes": 268435456,
"memory_peak_bytes": 3858432,
"oom_events": 0,
"oom_kill_events": 0,
"cpu_usage_usec": 9000,
"process_count": 0,
"process_limit": 10
}
},
"teardown": {
"reap_status": "clean",
"descendant_containment": "ok",
"zombie_count": 0,
"cleanup_verified": true,
"residual_mounts": [],
"collection_errors": []
}
}
}
Signature verification#
We follow the Standard Webhooks specification - the same pattern used by OpenAI, Stripe, and Svix.
Headers#
| Header | Value |
|---|---|
webhook-id | Unique message ID (the submission UUID) |
webhook-timestamp | Unix timestamp (seconds) |
webhook-signature | v1,<base64-encoded-hmac> |
Signed content#
The HMAC-SHA256 is computed over:
{webhook-id}.{webhook-timestamp}.{body}
Verification example (Python)#
import hmac, hashlib, base64
def verify_webhook(body: bytes, headers: dict, secret: str) -> bool:
msg_id = headers["webhook-id"]
timestamp = headers["webhook-timestamp"]
signature = headers["webhook-signature"]
signed_content = f"{msg_id}.{timestamp}.".encode() + body
expected = hmac.new(
secret.encode(), signed_content, hashlib.sha256
).digest()
expected_b64 = "v1," + base64.b64encode(expected).decode()
return hmac.compare_digest(signature, expected_b64)
Delivery behaviour#
- Webhooks are opt-in. No URL, no webhook.
- Secret is mandatory when URL is provided. The platform does not deliver unsigned webhooks.
- HTTPS required. Webhook URLs must use HTTPS.
- Non-blocking delivery. Webhook failures do not affect the submission result.
- 3 attempts. Immediate, then 1s delay, then 5s delay. Server errors (5xx) trigger retry; client errors (4xx) do not. Poll
/api/result/{id}as fallback if all attempts fail.
SSRF protection#
Webhook URLs are validated:
- Must use HTTPS
- Private IPs blocked:
10.x,172.16-31.x,192.168.x,169.254.x - Loopback blocked:
127.0.0.1,::1,localhost