Back to Blog
Engineering

Daniel Kim6 min
+W

When you use the Coasty Computer Use API to run Task Runs or Workflows, you can configure a webhook_url to receive real-time updates. The server sends each event as a Server-Sent Events stream, and each message is HMAC-signed. If you do not verify the signature, malicious actors could craft fake events and trigger your downstream pipelines. This guide shows you how to validate those HMACs using the Coasty webhook header, ensuring only legitimate events reach your application.

How webhook verification works

When a state changes for a Task Run or Workflow (e.g., queued, running, succeeded, failed, cancelled, or timed_out), the Coasty server pushes an event to your webhook_url. The request includes an HMAC-SHA256 signature in the header Coasty-Signature. The header format is t=<unix_timestamp>,v1=<hex_signature>. You compute the HMAC of the raw request body using your secret webhook key. If the hex value matches the v1 field and the timestamp is recent (within a few minutes), you accept the event. This pattern protects against replay attacks and ensures the payload came from Coasty.

python
import os
import hmac
import hashlib
import base64
import json
from datetime import datetime, timedelta

# Read your webhook secret from the environment
webhook_secret = os.getenv("COASTY_WEBHOOK_SECRET")

# Example payload received from Coasty
incoming_body = b'{"run_id": "run_abc123","status":"succeeded","events":[{"type":"run_state","state":"succeeded","timestamp":1700000000}]}'

# Extract signature header
header = incoming_headers.get("Coasty-Signature","")
if not header:
    raise ValueError("Missing Coasty-Signature header")

try:
    timestamp_str, signature_hex = header.split(",v1=")
    timestamp = int(timestamp_str.split("t=")[-1])
except Exception as e:
    raise ValueError(f"Invalid Coasty-Signature header format: {e}")

# Replay check: reject events older than 5 minutes
if datetime.now().timestamp() - timestamp > 300:
    raise ValueError("Webhook timestamp too old, possible replay")

# Compute HMAC-SHA256 of the raw body
signature_bytes = hmac.new(
    webhook_secret.encode("utf-8"),
    incoming_body,
    hashlib.sha256
).digest()
signature_b64 = base64.b64encode(signature_bytes).decode("utf-8")
signature_hex = hashlib.sha256(signature_bytes).hexdigest()

# Compare hex signatures (constant-time to avoid timing leaks)
if not hmac.compare_digest(signature_hex, signature_hex):
    raise ValueError("Webhook signature mismatch")

# Parse and process the payload
payload = json.loads(incoming_body)
run_id = payload.get("run_id")
status = payload.get("status")
print(f"Verified webhook for run {run_id}: status {status}")

Webhook header and timestamp details

  • The header name is Coasty-Signature (case-sensitive).
  • The header format is t=<unix_timestamp>,v1=<hex_signature>.
  • Reject events older than a few minutes to prevent replay attacks.
  • Use constant-time comparison (hmac.compare_digest) when validating signatures.
  • The signature is computed over the raw request body, not a JSON stringified version.

Always verify the timestamp and use constant-time signature comparison.

Where this beats brittle automation

Traditional automation relies on brittle selectors or API-only workflows. If a UI changes, the test fails. With a computer use agent, the bot sees the screen, reads text, and acts like a human. When the agent finishes a Task Run or Workflow, you receive a webhook event with the final state. Verifying that signature guarantees the event actually came from the agent, not from an attacker, so you can safely orchestrate downstream systems. This lets you build resilient automation that adapts to UI changes rather than breaking on them.

Add webhook verification to your Task Runs and Workflow pipelines to make them production-ready. Generate a webhook key at https://coasty.ai/developers, set COASTY_WEBHOOK_SECRET in your environment, and start receiving trusted events. Build robust computer use agents that you can trust in real-world workflows.

© 2026 Coasty

Backed byYCombinator