Receive generation webhooks
Register an HTTPS endpoint, verify signed generation events, and handle retries without duplicate work.
On this page
Webhooks notify your integration when a generation changes state. Register only the generation events you need and continue to treat the generation resource as the current source of truth.
Register a public HTTPS endpoint
Create an endpoint with an organization API key. The URL must use public HTTPS and must not resolve to a private or local address. The response returns a whsec_... signing secret once; store it securely.
Verify before processing
Verify EntireFeed-Signature against the exact raw request body before parsing JSON. The signed message is the timestamp, a period, and the raw body using HMAC-SHA256. Also record EntireFeed-Event-ID and ignore duplicate event IDs.
Return success promptly
Return any 2xx response after durable receipt. Failed deliveries are retried with the same event ID. Make your handler idempotent and fetch the generation when your workflow needs the latest state.
Only the generation events listed in the webhook reference are currently supported.
Register an endpoint
whsec_...curl https://api.entirefeed.com/v1/webhooks/endpoints \
-X POST \
-H "Authorization: Bearer $ENTIREFEED_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/webhooks/entirefeed",
"events": [
"generation.succeeded",
"generation.failed",
"generation.manual_review"
]
}'{
"id": "f419d0cf-5d8e-4b4c-82bb-6042b17e68f8",
"url": "https://example.com/webhooks/entirefeed",
"events": [
"generation.succeeded",
"generation.failed",
"generation.manual_review"
],
"secret": "whsec_store_this_value_once"
}List or deactivate endpoints
curl https://api.entirefeed.com/v1/webhooks/endpoints \
-X GET \
-H "Authorization: Bearer $ENTIREFEED_API_KEY"curl https://api.entirefeed.com/v1/webhooks/endpoints/f419d0cf-5d8e-4b4c-82bb-6042b17e68f8 \
-X DELETE \
-H "Authorization: Bearer $ENTIREFEED_API_KEY"Verify the signature
import { createHmac, timingSafeEqual } from "node:crypto";
const parts = Object.fromEntries(
signatureHeader.split(",").map((part) => part.split("=", 2)),
);
const timestamp = Number(parts.t);
const received = Buffer.from(parts.v1 ?? "", "hex");
const expected = Buffer.from(
createHmac("sha256", process.env.ENTIREFEED_WEBHOOK_SECRET!)
.update(Buffer.concat([Buffer.from(String(timestamp) + "."), rawBody]))
.digest("hex"),
"hex",
);
const fresh = Math.abs(Date.now() / 1000 - timestamp) <= 300;
const valid =
fresh &&
received.length === expected.length &&
timingSafeEqual(received, expected);Process deliveries idempotently
{
"id": "evt_0123456789abcdef0123456789abcdef",
"type": "generation.succeeded",
"created_at": "2026-07-12T08:31:14Z",
"data": {
"object": {
"id": "gen_0123456789abcdef0123456789abcdef",
"object": "generation",
"status": "succeeded",
"model": "seedance-2",
"task": "video.create",
"created_at": "2026-07-12T08:30:00Z",
"completed_at": "2026-07-12T08:31:14Z",
"output": {
"assets": [
{
"id": "asset_0123456789abcdef0123456789abcdef",
"type": "video",
"url": "https://cdn.entirefeed.com/examples/product-reveal.mp4",
"mime_type": "video/mp4",
"expires_at": null
}
]
},
"usage": {
"amount_usd": "1.20",
"line_items": [],
"billing_status": "settled"
},
"error": null,
"metadata": null
}
}
}Store the event ID before returning 2xx. Repeated delivery of that ID must not repeat customer-side effects.