Manage workspace webhooks with HMAC-SHA256 signature verification for secure event notifications. Receive real-time updates about generation completions, errors, and other events.
import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
const client = new ElevenLabsClient({ apiKey: "your-api-key" });
// Access this API via: client.webhooksCreate a new webhook for workspace events.
/**
* @param request - Webhook URL and configuration
* @param requestOptions - Optional request configuration
* @returns Created webhook with secret
* @throws UnprocessableEntityError if request fails
*/
client.webhooks.create(
request: BodyCreateWorkspaceWebhookV1WorkspaceWebhooksPost,
requestOptions?: RequestOptions
): HttpResponsePromise<WorkspaceCreateWebhookResponseModel>;
interface BodyCreateWorkspaceWebhookV1WorkspaceWebhooksPost {
/** Webhook URL to receive events */
url: string;
/** Array of event types to subscribe to */
events?: string[];
}
interface WorkspaceCreateWebhookResponseModel {
/** Webhook ID */
webhook_id: string;
/** Webhook URL */
url: string;
/** Webhook secret for signature verification */
secret: string;
/** Subscribed event types */
events: string[];
}List all workspace webhooks.
/**
* @param request - Optional parameters
* @param requestOptions - Optional request configuration
* @returns List of webhooks
* @throws UnprocessableEntityError if request fails
*/
client.webhooks.list(
request?: WebhooksListRequest,
requestOptions?: RequestOptions
): HttpResponsePromise<WorkspaceWebhookListResponseModel>;
interface WebhooksListRequest {
/** Include usage statistics */
includeUsages?: boolean;
}
interface WorkspaceWebhookListResponseModel {
webhooks: WorkspaceWebhook[];
}
interface WorkspaceWebhook {
webhook_id: string;
url: string;
events: string[];
created_at: number;
usage_count?: number;
last_used_at?: number;
}Update webhook configuration.
/**
* @param webhook_id - Webhook ID
* @param request - Updated webhook configuration
* @param requestOptions - Optional request configuration
* @returns Updated webhook metadata
* @throws UnprocessableEntityError if request fails
*/
client.webhooks.update(
webhook_id: string,
request: BodyUpdateWorkspaceWebhookV1WorkspaceWebhooksWebhookIdPatch,
requestOptions?: RequestOptions
): HttpResponsePromise<PatchWorkspaceWebhookResponseModel>;
interface BodyUpdateWorkspaceWebhookV1WorkspaceWebhooksWebhookIdPatch {
/** New webhook URL */
url?: string;
/** Updated event types */
events?: string[];
}
interface PatchWorkspaceWebhookResponseModel {
webhook_id: string;
url: string;
events: string[];
}Delete a webhook.
/**
* @param webhook_id - Webhook ID
* @param requestOptions - Optional request configuration
* @returns Deletion confirmation
* @throws UnprocessableEntityError if request fails
*/
client.webhooks.delete(
webhook_id: string,
requestOptions?: RequestOptions
): HttpResponsePromise<DeleteWorkspaceWebhookResponseModel>;
interface DeleteWorkspaceWebhookResponseModel {
success: boolean;
}Verify webhook signature and construct event.
/**
* Verify webhook signature using HMAC-SHA256
* @param rawBody - Raw request body as string
* @param sigHeader - Signature header value
* @param secret - Webhook secret
* @returns Parsed and verified event
* @throws Error if signature is invalid or timestamp is too old
*/
client.webhooks.constructEvent(
rawBody: string,
sigHeader: string,
secret: string
): Promise<WebhookEvent>;
/**
* Webhook event structure
*/
interface WebhookEvent {
/** Event type identifier */
event_type: string;
/** Event data payload */
data: Record<string, any>;
/** Timestamp when event was created */
created_at: string;
}Common webhook event types:
generation.completed - Audio generation completed successfullygeneration.failed - Audio generation faileddubbing.completed - Dubbing job completeddubbing.failed - Dubbing job failedvoice.created - Voice was createdvoice.deleted - Voice was deletedimport { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
const client = new ElevenLabsClient({ apiKey: "your-api-key" });
// Create webhook for generation events
const webhook = await client.webhooks.create({
url: "https://your-server.com/webhook",
events: ["generation.completed", "generation.failed"],
});
console.log("Webhook ID:", webhook.webhook_id);
console.log("Secret:", webhook.secret); // Store securely!// Get all webhooks with usage stats
const webhooks = await client.webhooks.list({
includeUsages: true,
});
for (const webhook of webhooks.webhooks) {
console.log(`${webhook.webhook_id}: ${webhook.url}`);
console.log(` Events: ${webhook.events.join(", ")}`);
if (webhook.usage_count !== undefined) {
console.log(` Calls: ${webhook.usage_count}`);
}
}// Update webhook URL or events
await client.webhooks.update("webhook-id", {
url: "https://new-server.com/webhook",
events: ["generation.completed", "dubbing.completed"],
});// Remove webhook
await client.webhooks.delete("webhook-id");import express from "express";
const app = express();
// Important: Use express.raw() to get raw body for signature verification
app.post(
"/webhook",
express.raw({ type: "application/json" }),
async (req, res) => {
const signature = req.headers["elevenlabs-signature"] as string;
const webhookSecret = process.env.WEBHOOK_SECRET!;
try {
// Verify signature and parse event
const event = await client.webhooks.constructEvent(
req.body.toString(),
signature,
webhookSecret
);
console.log("Verified event:", event);
// Handle event
switch (event.type) {
case "generation.completed":
console.log("Generation completed:", event.data);
break;
case "generation.failed":
console.error("Generation failed:", event.data);
break;
default:
console.log("Unhandled event type:", event.type);
}
res.json({ received: true });
} catch (error) {
console.error("Webhook verification failed:", error);
res.status(400).send("Webhook verification failed");
}
}
);
app.listen(3000);// pages/api/webhook.ts
import type { NextApiRequest, NextApiResponse } from "next";
import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
const client = new ElevenLabsClient({ apiKey: process.env.ELEVENLABS_API_KEY! });
// Disable Next.js body parsing to get raw body
export const config = {
api: {
bodyParser: false,
},
};
async function getRawBody(req: NextApiRequest): Promise<string> {
const chunks: Buffer[] = [];
for await (const chunk of req) {
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
}
return Buffer.concat(chunks).toString("utf8");
}
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== "POST") {
return res.status(405).send("Method not allowed");
}
const signature = req.headers["elevenlabs-signature"] as string;
const rawBody = await getRawBody(req);
try {
const event = await client.webhooks.constructEvent(
rawBody,
signature,
process.env.WEBHOOK_SECRET!
);
// Handle event
console.log("Webhook event:", event);
res.status(200).json({ received: true });
} catch (error) {
console.error("Webhook error:", error);
res.status(400).send("Webhook verification failed");
}
}async function handleWebhookEvent(event: any): Promise<void> {
switch (event.type) {
case "generation.completed":
const { history_item_id, voice_id, text } = event.data;
console.log("Generation completed:", history_item_id);
// Download the audio
const audio = await client.history.getAudio(history_item_id);
// Process audio...
break;
case "generation.failed":
console.error("Generation failed:", event.data.error);
// Handle failure...
break;
case "dubbing.completed":
const { dubbing_id, target_languages } = event.data;
console.log("Dubbing completed:", dubbing_id);
// Download dubbed audio for each language
for (const lang of target_languages) {
const audio = await client.dubbing.audio.get(dubbing_id, lang);
// Save audio...
}
break;
}
}// Server-side: Implement retry logic for webhook delivery
async function sendWebhook(
url: string,
event: any,
secret: string,
maxRetries = 3
): Promise<void> {
const timestamp = Math.floor(Date.now() / 1000);
const payload = JSON.stringify(event);
// Create signature: HMAC-SHA256(timestamp + "." + payload)
const crypto = require("crypto");
const signature = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${payload}`)
.digest("hex");
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"elevenlabs-signature": `t=${timestamp},v1=${signature}`,
},
body: payload,
});
if (response.ok) {
console.log("Webhook delivered successfully");
return;
}
console.log(`Webhook delivery failed (attempt ${attempt + 1}/${maxRetries})`);
} catch (error) {
console.error(`Webhook error (attempt ${attempt + 1}/${maxRetries}):`, error);
}
// Wait before retry (exponential backoff)
if (attempt < maxRetries - 1) {
await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, attempt)));
}
}
throw new Error("Webhook delivery failed after all retries");
}// Log all webhook events to database
interface WebhookLog {
id: string;
webhook_id: string;
event_type: string;
payload: any;
timestamp: Date;
verified: boolean;
}
async function logWebhookEvent(
webhookId: string,
event: any,
verified: boolean
): Promise<void> {
const log: WebhookLog = {
id: generateId(),
webhook_id: webhookId,
event_type: event.type,
payload: event,
timestamp: new Date(),
verified,
};
// Save to database
await saveToDatabase(log);
}
// In webhook handler
app.post("/webhook", async (req, res) => {
const signature = req.headers["elevenlabs-signature"] as string;
let verified = false;
try {
const event = await client.webhooks.constructEvent(
req.body.toString(),
signature,
webhookSecret
);
verified = true;
await logWebhookEvent("webhook-id", event, verified);
// Handle event...
res.json({ received: true });
} catch (error) {
await logWebhookEvent("webhook-id", req.body, verified);
res.status(400).send("Verification failed");
}
});// Create webhook for all major events
const webhook = await client.webhooks.create({
url: "https://your-server.com/elevenlabs-webhook",
events: [
"generation.completed",
"generation.failed",
"dubbing.completed",
"dubbing.failed",
"voice.created",
"voice.deleted",
],
});// Send test event to webhook
async function testWebhook(webhookUrl: string): Promise<void> {
const testEvent = {
type: "test",
data: {
message: "This is a test webhook event",
timestamp: Date.now(),
},
};
const response = await fetch(webhookUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(testEvent),
});
if (response.ok) {
console.log("Test webhook delivered successfully");
} else {
console.error("Test webhook failed:", await response.text());
}
}
await testWebhook("https://your-server.com/webhook");