SKILL.md
Clay Webhooks & Events
Overview
Clay's event-driven architecture has two webhook patterns: (1) Inbound webhooks -- you POST data into Clay tables via unique webhook URLs, and (2) Outbound HTTP API columns -- Clay POSTs enriched data to your endpoint after enrichment completes. This skill covers both patterns with production-ready handlers.
Prerequisites
- Clay table with webhook source configured (for inbound)
- Clay table with HTTP API enrichment column (for outbound)
- HTTPS endpoint accessible from the internet
- Familiarity with Express.js or similar framework
Instructions
Step 1: Inbound Webhook -- Send Data into Clay
Every Clay table has a unique webhook URL. When you POST JSON to this URL, a new row appears in the table.
// src/clay/inbound.ts — send data into Clay tables
class ClayInboundWebhook {
constructor(
private webhookUrl: string,
private submissionCount: number = 0,
private readonly LIMIT: number = 50_000,
) {}
async sendRow(data: Record<string, unknown>): Promise<void> {
if (this.submissionCount >= this.LIMIT) {
throw new Error(`Webhook exhausted (${this.LIMIT} submissions). Create a new webhook in Clay.`);
}
const res = await fetch(this.webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (!res.ok) {
throw new Error(`Clay webhook failed: ${res.status} ${res.statusText}`);
}
this.submissionCount++;
}
async sendBatch(rows: Record<string, unknown>[], delayMs = 200): Promise<{ sent: number; failed: number }> {
let sent = 0, failed = 0;
for (const row of rows) {
try {
await this.sendRow(row);
sent++;
} catch {
failed++;
}
await new Promise(r => setTimeout(r, delayMs));
}
return { sent, failed };
}
}
// Usage
const webhook = new ClayInboundWebhook(process.env.CLAY_WEBHOOK_URL!);
await webhook.sendRow({
email: '[email protected]',
domain: 'acme.com',
first_name: 'Jane',
last_name: 'Doe',
source: 'website-form',
});
