Verifying Computer Use API Webhooks with HMAC Signatures
When you configure a webhook_url on a task run or workflow, Coasty pushes status updates as Server-Sent Events (SSE). These events include critical state changes like queued, running, succeeded, failed, or cancelled. Without validation, a malicious actor could forge a payload and trigger downstream actions or expose secrets. Verifying webhook integrity with HMAC signatures protects your system from tampering and ensures you only act on events that originate from Coasty.
How it works
Coasty signs each webhook event with an HMAC SHA256 hash using the shared key you generate in the Coasty dashboard. The signature is sent in a header named Coasty-Signature with the format t=<unix timestamp>,v1=<hex signature>. When your server receives the event, it recomputes the hash from the raw body bytes and compares it against the provided value. If they match and the timestamp is within a small tolerance (e.g., 300 seconds), you can trust the payload. This applies to all webhook endpoints, including GET /v1/runs/{id}/events streams and workflow run callbacks.
# Example webhook verification in Python (Flask)
import os
import hmac
import hashlib
import base64
from datetime import datetime, timedelta
from flask import Flask, request, jsonify
COASTY_API_KEY = os.getenv("COASTY_API_KEY")
App = Flask(__name__)
HMAC_ALGO = "sha256"
ALLOWED_TIME_DELTA = timedelta(seconds=300) # 5 minutes
@App.route("/webhook/runs", methods=["GET", "POST"])
def webhook_runs():
# 1. Extract signature header and timestamp
signature_header = request.headers.get("Coasty-Signature")
if not signature_header:
return jsonify({"error": "missing signature"}), 401
try:
timestamp_str, signature_hex = signature_header.split(",")
timestamp = int(timestamp_str.split("=")[1])
except Exception as e:
return jsonify({"error": "invalid signature format"}), 401
# 2. Reject stale events
if datetime.utcnow().timestamp() - timestamp > ALLOWED_TIME_DELTA.total_seconds():
return jsonify({"error": "stale event"}), 403
# 3. Compute HMAC on raw body bytes
body = request.get_data()
computed = hmac.new(
COASTY_API_KEY.encode("utf-8"),
body,
hashlib.sha256
).hexdigest()
# 4. Compare in constant time
if not hmac.compare_digest(computed, signature_hex):
return jsonify({"error": "signature mismatch"}), 401
# 5. Process the verified payload
payload = request.json
run_id = payload.get("id")
new_state = payload.get("state")
# here you would update your database, call downstream services, etc.
return jsonify({"status": "ok", "run_id": run_id, "state": new_state})
if __name__ == "__main__":
App.run(port=5000, debug=True)Coasty-Signature header format
- ●Header name is Coasty-Signature.
- ●Value is a comma-separated pair: t=<unix timestamp>,v1=<hex signature>.
- ●Timestamp is the seconds since Unix epoch.
- ●v1 is the lowercase hex representation of the HMAC SHA256 digest.
- ●Both fields are URL-encoded if necessary, but the standard format does not require encoding.
Always verify the Coasty-Signature header before processing any webhook payload.
Where this beats brittle automation
Traditional automation often relies on brittle selectors and polling APIs. When UI changes or elements move, scripts break and require frequent maintenance. With Coasty's computer use API, the agent sees the screen and acts like a human, making it resilient to layout shifts. Webhook verification adds a security layer on top of that flexibility, ensuring that only genuine state changes trigger downstream logic. This combination lets you build robust workflows that handle edge cases without brittle selectors.
Add webhook verification to your Coasty task runs and workflows to protect your production systems. Use the HMAC header and timestamp check to reject tampered events. After you secure your callbacks, explore more advanced features like workflows with conditional branches and human approvals. Get started by generating a key at https://coasty.ai/developers.