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#

  1. You create a webhook endpoint on a project in the dashboard
  2. You submit code with an API key scoped to that project
  3. Your code executes in the sandbox
  4. The platform POSTs the result to active project webhooks with HMAC-SHA256 signature headers
  5. 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://rustbox.orkait.com/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",
  "language": "python",
  "job_status": "completed",
  "schema_version": "1.0",
  "verdict": "AC",
  "exit_code": 0,
  "signal": null,
  "stdout": "42\n",
  "stderr": "",
  "output_integrity": "complete",
  "error_message": null,
  "cpu_time_secs": 0.009,
  "wall_time_secs": 0.025,
  "memory_peak_bytes": 3858432,
  "evidence": {
    "verdict_cause": "normal_exit",
    "verdict_actor": "runtime",
    "isolation_mode": "strict",
    "controls_applied": ["pid_namespace", "mount_namespace", "network_namespace", "memory_limit", "process_limit", "no_new_privileges"],
    "controls_missing": [],
    "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
    },
    "timing": {
      "cpu_ms": 9,
      "wall_ms": 25,
      "cpu_wall_ratio": 0.36,
      "divergence": "cpu_bound"
    },
    "process_lifecycle": {
      "reap_status": "clean",
      "descendant_containment": "ok",
      "zombie_count": 0
    },
    "judge_actions": [],
    "collection_errors": []
  },
  "created_at": "2026-04-02T12:00:00.000Z",
  "started_at": "2026-04-02T12:00:00.001Z",
  "completed_at": "2026-04-02T12:00:00.026Z"
}

Signature verification#

We follow the Standard Webhooks specification - the same pattern used by OpenAI, Stripe, and Svix.

Headers#

HeaderValue
webhook-idUnique message ID (the submission UUID)
webhook-timestampUnix timestamp (seconds)
webhook-signaturev1,<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