Webhooks
Register webhooks to receive updates on the workflow execution
You can register webhooks for each workflow to receive result, status update and observability events.
Here are the different events you can subscribe to:
This event is sent once when the execution of the workflow starts.
{
"payload": {
"session_id": "60673dc6-92a1-428a-ba51-8c3f324daea8",
"workflow_id": "00b82d36-8243-4a95-a0ca-e64096226731"
},
"timestamp": "1724002463",
"expires_at": "1724002763",
"event": "execution.start"
}
This event is sent every time the workflow is executing an action like clicking on a button or filling out an input field.
{
"payload": {
"session_id": "60673dc6-92a1-428a-ba51-8c3f324daea8",
"workflow_id": "00b82d36-8243-4a95-a0ca-e64096226731",
"current_step": "Type user name",
"next_step": "Click Next"
},
"timestamp": "1724002463",
"expires_at": "1724002763",
"event": "execution.step"
}
This event is sent once when the execution of the workflow has finished successfully. It includes the result data that is returned from the workflow.
{
"payload": {
"session_id": "60673dc6-92a1-428a-ba51-8c3f324daea8",
"workflow_id": "00b82d36-8243-4a95-a0ca-e64096226731",
"data": {
"your": "defined",
"output": "datamodel",
},
"errors": [],
"status": "Success",
"queued_sessions": 2,
"running_session:": 1,
"input_variables": {
"$USER_NAME": "adrian"
}
},
"timestamp": "1724002463",
"expires_at": "1724002763",
"event": "execution.success"
}
This event is sent once the execution of the workflow fails and contains information about the error.
{
"payload": {
"session_id": "60673dc6-92a1-428a-ba51-8c3f324daea8",
"workflow_id": "00b82d36-8243-4a95-a0ca-e64096226731",
"data": {},
"errors": [{
"message": "Error while clicking on element. Timeout after 15000ms waiting for element '//html/body[1]/div/div/div/div/div[2]/div/main/div[2]/div/div[2]/form/div[2]/input' of type 'XPATH' to load on https://www.cloudcruise.com",
"error_id": "c4c7d4fe-b00c-49d8-ab67-40808acea59c",
"error_code": "E001: LOGIN_FAILED",
}],
"status": "Error",
"queued_sessions": 0,
"running_session:": 0,
"input_variables": {
"$USER_NAME": "adrian"
}
},
"timestamp": "1724002463",
"expires_at": "1724002763",
"event": "execution.failed"
}
This event is sent when one or several workflow runs were cancelled. It respectively either contains session_id
of the cancelled run or session_ids
that contains all of the cancelled runs.
Response for single run cancelled:
{
"payload": {
"workflow_id": "00b82d36-8243-4a95-a0ca-e64096226731",
"message": "User interrupted workflow. Session was stopped.",
"error_code": "INTERRUPTED-E0001",
"session_id": "session_id_1"
},
"timestamp": "1724002463",
"expires_at": "1724002763",
"event": "execution.stopped"
}````
Response for multiple cancelled at once:
```json
{
"payload": {
"message": "Execution was interrupted because another workflow was executed with the same credentials, but failed its login attempt. This is to prevent repeated incorrect login attempts.",
"error_code": "INTERRUPTED-E0002",
"session_causing_error": "60673dc6-92a1-428a-ba51-8c3f324daea8",
"session_ids": ["session_id_1", "session_id_2"]
},
"timestamp": "1724002463",
"expires_at": "1724002763",
"event": "execution.stopped"
}
This event is sent when the upload of the screenshot has finished.
{
"payload": {
"session_id": "60673dc6-92a1-428a-ba51-8c3f324daea8",
"workflow_id": "00b82d36-8243-4a95-a0ca-e64096226731",
"screenshot_id": "6fdfad4a-3064-48cf-99f0-3d8d271f2c8e",
"signed_screenshot_url": "https://cloudcruise.com/screenshots/...",
"signed_screenshot_url_expires": "2024-08-25T17:35:00.432Z"
},
"timestamp": "1724002463",
"expires_at": "1724002763",
"event": "screenshot.uploaded"
}
This event is sent when the upload of the workflow video has finished.
{
"payload": {
"session_id": "60673dc6-92a1-428a-ba51-8c3f324daea8",
"workflow_id": "00b82d36-8243-4a95-a0ca-e64096226731",
"signed_screen_recording_url": "https://cloudcruise.com/...",
"signeed_screen_recording_url_expires": "2025-06-01T22:21:00.371579+00:00",
"timestamp": "2025-05-25T22:21:00.371579+00:00"
},
"timestamp": "1724002463",
"expires_at": "1724002763",
"event": "video.uploaded"
}
This event is sent when a file has been uploaded during workflow execution.
{
"payload": {
"signed_file_url": "https://example.com/storage/v1/object/sign/files/123/456/example.zip?token=dummy-token",
"file_name": "example_file.zip",
"timestamp": "2024-03-20T12:00:00.000Z",
"signed_file_url_expires": "2024-03-27T12:00:00.000Z",
"metadata": {},
"session_id": "session-123",
"workflow_id": "workflow-456"
},
"timestamp": 1710936000,
"expires_at": 1710936300,
"event": "file.uploaded"
}
This event is sent when user input is required to continue the workflow.
{
"payload": {
"current_step": "Enter 2FA Code",
"missing_properties": ["two_factor_code"],
"message": "Please enter the 6-digit code sent to your phone",
"session_id": "session-123",
"workflow_id": "workflow-456",
"expected_json_schema_datamodel": {
"type": "object",
"properties": {
"two_factor_code": {
"type": "string",
"pattern": "^\\d{6}$",
"description": "6-digit verification code"
}
},
"required": ["two_factor_code"]
}
},
"timestamp": 1710936000,
"expires_at": 1710936300,
"event": "interaction.waiting"
}
This event is sent when the user input was not provided in time or in a wrong format.
{
"timestamp": "1724002463",
"expires_at": "1724002763",
"event": "video.uploaded"
}
This event is sent when the user input was provided and the workflow continues.
{
"timestamp": "1724002463",
"expires_at": "1724002763",
"event": "video.uploaded"
}
Simply navigate to our settings page to get started.
Security
We sign webhook events with the secret you get when you register a webhook. We also add an expiry time. You can change the expiry time in the setting of the registered webhook.
Here’s how we recommend to verify the message you receive from us:
import time
import hashlib
import hmac
import json
class VerificationError(Exception):
"""Custom exception to handle verification errors."""
def **init**(self, message="Verification failed", status_code=400):
super().**init**(message)
self.status_code = status_code
def verify_hmac(received_data, received_signature, secret_key): # Ensure the received data is in bytes, necessary for HMAC
if isinstance(received_data, str):
received_data = received_data.encode()
# Generate the HMAC new object with the secret key and SHA-256
calculated_signature = hmac.new(secret_key.encode(), received_data, hashlib.sha256).hexdigest()
# Safely compare the computed HMAC with the received HMAC
return hmac.compare_digest(calculated_signature, received_signature)
def verify_message(received_data, received_signature, secret_key):
if not received_data:
raise VerificationError("Received request without body", 400)
try:
data_json = json.loads(received_data.decode('utf-8'))
except json.JSONDecodeError as e:
raise VerificationError("Failed to decode json: " + str(e), 400)
# Check if the expiration is sent
expires_at = data_json.get("expires_at")
if not expires_at:
raise VerificationError("No expiration date sent", 400)
# Verify HMAC first
if not verify_hmac(received_data, received_signature, secret_key):
raise VerificationError("Invalid HMAC signature.", 401)
# Check if the message is not expired
if time.time() > expires_at:
raise VerificationError("Webhook message expired.", 400)
return data_json
import time
import hashlib
import hmac
import json
class VerificationError(Exception):
"""Custom exception to handle verification errors."""
def **init**(self, message="Verification failed", status_code=400):
super().**init**(message)
self.status_code = status_code
def verify_hmac(received_data, received_signature, secret_key): # Ensure the received data is in bytes, necessary for HMAC
if isinstance(received_data, str):
received_data = received_data.encode()
# Generate the HMAC new object with the secret key and SHA-256
calculated_signature = hmac.new(secret_key.encode(), received_data, hashlib.sha256).hexdigest()
# Safely compare the computed HMAC with the received HMAC
return hmac.compare_digest(calculated_signature, received_signature)
def verify_message(received_data, received_signature, secret_key):
if not received_data:
raise VerificationError("Received request without body", 400)
try:
data_json = json.loads(received_data.decode('utf-8'))
except json.JSONDecodeError as e:
raise VerificationError("Failed to decode json: " + str(e), 400)
# Check if the expiration is sent
expires_at = data_json.get("expires_at")
if not expires_at:
raise VerificationError("No expiration date sent", 400)
# Verify HMAC first
if not verify_hmac(received_data, received_signature, secret_key):
raise VerificationError("Invalid HMAC signature.", 401)
# Check if the message is not expired
if time.time() > expires_at:
raise VerificationError("Webhook message expired.", 400)
return data_json
const crypto = require("crypto");
class VerificationError extends Error {
constructor(message = "Verification failed", statusCode = 400) {
super(message);
this.statusCode = statusCode;
this.name = "VerificationError";
}
}
function verifyHmac(receivedData, receivedSignature, secretKey) {
const hmac = crypto.createHmac("sha256", secretKey);
hmac.update(receivedData);
const calculatedSignature = hmac.digest("hex");
// Remove the 'Sha256=' prefix from the receivedSignature before comparing
const formattedReceivedSignature = receivedSignature.split("=")[1];
// Ensure both signatures are of equal length and hex encoded
if (formattedReceivedSignature.length !== calculatedSignature.length) {
return false;
}
return crypto.timingSafeEqual(
Buffer.from(calculatedSignature, "hex"),
Buffer.from(formattedReceivedSignature, "hex")
);
}
function verifyMessage(receivedData, receivedSignature, secretKey) {
if (!receivedData) {
throw new VerificationError("Received request without body", 400);
}
let dataJson;
try {
dataJson = JSON.parse(receivedData.toString("utf-8"));
} catch (error) {
throw new VerificationError("Failed to decode json: " + error.message, 400);
}
const expiresAt = dataJson.expires_at;
if (!expiresAt) {
throw new VerificationError("No expiration date sent", 400);
}
if (!verifyHmac(receivedData, receivedSignature, secretKey)) {
console.log(
"Invalid HMAC signature.",
receivedData,
receivedSignature,
secretKey
);
throw new VerificationError("Invalid HMAC signature.", 401);
}
if (Date.now() / 1000 > expiresAt) {
throw new VerificationError("Webhook message expired.", 400);
}
return dataJson;
}
Deliverability
CloudCruise tries resending webhooks 2 times. You can also trigger a manual resend over your CloudCruise dashboard in the run detail view. Alternatively, you can send a POST request to api.cloudcruise.com/webhooks/replay/{your_session_id}
.