Skip to main content
WEBHOOK
events
{
  "type": "<string>",
  "data": {
    "id": "<string>",
    "workflow": {
      "id": "<string>",
      "name": "<string>"
    },
    "batch": {
      "id": "<string>",
      "name": "<string>"
    },
    "logins": [
      {
        "id": "<string>",
        "name": "<string>"
      }
    ],
    "authentications": [
      {
        "browserSessionId": "<string>",
        "status": "<string>",
        "liveViewUrl": "<string>"
      }
    ],
    "startTime": "2023-11-07T05:31:56Z",
    "params": {},
    "metadata": {},
    "name": "<string>",
    "startBlock": {
      "id": "<string>",
      "name": "<string>",
      "blockReferenceId": "<string>",
      "type": "<string>",
      "isCacheable": true,
      "skipIfNotPresent": true,
      "useWorkflowRecoveryAgent": true,
      "data": {
        "loginId": "<string>",
        "isParameter": true,
        "forceLoginEveryTime": true
      },
      "parentRelationship": {
        "blockDepth": 123,
        "positionInSequence": 123,
        "parentBlockId": "<string>",
        "parentSequenceId": "sequence"
      },
      "transformerFn": "<string>",
      "locator": {
        "role": "<string>",
        "text": "<string>",
        "label": "<string>",
        "placeholder": "<string>",
        "altText": "<string>",
        "title": "<string>",
        "css": "<string>",
        "xpath": "<string>"
      }
    },
    "contextAuthentications": [
      {
        "contextAuthenticationId": "<string>",
        "loginSummary": {
          "loginId": "<string>",
          "name": "<string>",
          "isManual": true
        },
        "authenticationStatus": "Unauthenticated"
      }
    ],
    "sessionId": "<string>",
    "attempts": 123,
    "totalAttempts": 123,
    "isLastAttempt": true,
    "durationMs": 123,
    "endTime": "2023-11-07T05:31:56Z",
    "response": {},
    "truncatedResponse": "<string>",
    "responseFile": {
      "id": "<string>",
      "name": "<string>",
      "mimeType": "<string>",
      "sizeBytes": 123,
      "downloadUrl": "<string>",
      "key": "<string>"
    },
    "browserSessionId": "<string>",
    "browserSessionVendorId": "<string>",
    "errorAnalysis": "<string>",
    "summary": "<string>",
    "status": "Completed",
    "context": {},
    "downloads": {
      "status": "pending",
      "files": [
        {
          "id": "<string>",
          "name": "<string>",
          "mimeType": "<string>",
          "sizeBytes": 123,
          "downloadUrl": "<string>",
          "key": "<string>"
        }
      ],
      "message": "<string>"
    },
    "parentExecutionId": "<string>"
  }
}

Secure your endpoint

Verify that all webhook requests are generated by Kaizen by checking the signature in each request’s X-Webhooks-Signature header.

Verify the signature

Each webhook request includes these headers:
  • X-Webhooks-Signature: HMAC signature (includes version prefix)
  • X-Webhooks-Timestamp: Unix timestamp in seconds
  • X-Webhooks-Id: Unique identifier for this webhook delivery
To verify a signature:
  1. Extract the signature from X-Webhooks-Signature (remove the version prefix)
  2. Get the timestamp from X-Webhooks-Timestamp
  3. Get the webhook ID from X-Webhooks-Id
  4. Read the raw request body as a string (before JSON parsing)
  5. Construct the signed payload: {webhookId}.{timestamp}.{raw_body}
  6. Compute HMAC-SHA256 of the signed payload using your decoded secret
  7. Compare the computed signature with the received signature using constant-time comparison

Code Examples

const crypto = require('crypto');

function verifyWebhookSignature(
  webhookId,
  payload,
  signature,
  timestamp,
  secret
) {
  const signedPayload = `${webhookId}.${timestamp}.${payload}`;
  const keyBytes = Buffer.from(secret, 'base64url');
  const expectedSignature = crypto
    .createHmac('sha256', keyBytes)
    .update(signedPayload, 'utf8')
    .digest('hex');

  if (signature.length !== expectedSignature.length) {
    return false;
  }

  return crypto.timingSafeEqual(
    Buffer.from(signature, 'hex'),
    Buffer.from(expectedSignature, 'hex')
  );
}

app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const webhookId = req.headers['x-webhooks-id'];
  const signatureHeader = req.headers['x-webhooks-signature'];
  const timestamp = req.headers['x-webhooks-timestamp'];

  if (!webhookId || !signatureHeader || !timestamp) {
    return res.status(401).json({ error: 'Missing required headers' });
  }

  const signature = signatureHeader.replace(/^v\d+=/, '');
  const payload = req.body.toString();
  const webhookSecret = process.env.WEBHOOK_SECRET;

  if (
    !webhookSecret ||
    !verifyWebhookSignature(
      webhookId,
      payload,
      signature,
      timestamp,
      webhookSecret
    )
  ) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const event = JSON.parse(payload);
  // Process the webhook event
  res.status(200).json({ received: true });
});

Body

application/json

Payload sent when an execution has finished running

type
string
required
Allowed value: "execution.complete"
data
Completed Execution · object
required

Data about the completed execution.

Response

200

Webhook received successfully