Skip to main content
Manage webhook endpoints for the authenticated workspace.
clay webhooks <create|list|delete|test>
Use the returned webhook id with clay routines runs start --webhook-id. Clay sends a signed POST request when the run is ready to check.

Commands

clay webhooks create https://example.com/hooks/clay
clay webhooks list --limit 100
clay webhooks delete wh_abc123
clay webhooks test wh_abc123
create returns the signing secret exactly once:
{
  "id": "wh_abc123",
  "url": "https://example.com/hooks/clay",
  "createdAt": "2026-06-16T17:50:00.000Z",
  "signingSecret": "whsec_..."
}

Delivery payload

{
  "webhookId": "wh_abc123",
  "createdAt": "2026-06-16T17:50:00.000Z",
  "data": {
    "routine_run_id": "run_abc123"
  }
}
For test events, data is {}. The routine_run_id can identify an inline run or a bulk run. If one webhook handles both, store the run mode when you start the run, or use clay routines runs get, which fetches results for either mode.

Verify and fetch results

Clay signs the exact request body with your webhook signing secret and sends it in X-Clay-Signature.
import crypto from 'node:crypto';
import express from 'express';

const app = express();

function sign(body) {
  return `sha256=${crypto
    .createHmac('sha256', process.env.CLAY_WEBHOOK_SIGNING_SECRET)
    .update(body, 'utf8')
    .digest('hex')}`;
}

function signaturesMatch(actual, expected) {
  const actualBuffer = Buffer.from(actual);
  const expectedBuffer = Buffer.from(expected);
  return actualBuffer.length === expectedBuffer.length && crypto.timingSafeEqual(actualBuffer, expectedBuffer);
}

async function fetchRunResults(routineRunId) {
  // For batch routine runs, use `/public/v0/routines/run-batch/${routineRunId}/results`.
  const result = await fetch(`https://api.clay.com/public/v0/routines/run/${routineRunId}/results`, {
    headers: { 'clay-api-key': process.env.CLAY_API_KEY },
  });

  return result.json();
}

app.post('/hooks/clay', express.raw({ type: 'application/json' }), async (req, res) => {
  const body = req.body.toString('utf8');
  const actualSignature = req.get('X-Clay-Signature') ?? '';
  const expectedSignature = sign(body);

  if (!signaturesMatch(actualSignature, expectedSignature)) {
    return res.sendStatus(401);
  }

  const event = JSON.parse(body);
  const routineRunId = event.data.routine_run_id;
  const run = await fetchRunResults(routineRunId);

  return res.sendStatus(204);
});
Webhook delivery is not guaranteed. Use webhooks to react faster, but keep polling clay routines runs get as a fallback.