> ## Documentation Index
> Fetch the complete documentation index at: https://docs.eversince.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Receive real-time status updates instead of polling.

Webhooks notify your server when a project's status changes, eliminating the need to poll.

## Setup

Set a webhook URL when creating a project or via settings:

```json theme={"dark"}
{
  "brief": "Your brief here",
  "webhook_url": "https://your-server.com/webhooks/eversince"
}
```

Or add one to an existing project:

```bash theme={"dark"}
curl -X PATCH https://eversince.ai/api/v1/projects/proj_abc123/settings \
  -H "Authorization: Bearer $EVERSINCE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "webhook_url": "https://your-server.com/webhooks/eversince" }'
```

The URL must use HTTPS.

## Events

Webhooks fire on these status transitions:

| Event        | When                                             |
| ------------ | ------------------------------------------------ |
| `running`    | Agent started working                            |
| `generating` | Waiting for model outputs                        |
| `idle`       | Work complete. Result ready or awaiting feedback |
| `failed`     | Something went wrong                             |
| `rendering`  | Final video render in progress                   |

Cancelled projects do not fire webhook events.

## Payload

Each webhook delivers the full project state (the same data returned by `GET /projects/:id`) plus an `event` field indicating the status transition that triggered the webhook.

```json theme={"dark"}
{
  "id": "proj_abc123",
  "status": "idle",
  "mode": "autonomous",
  "output_type": "assembled",
  "event": "idle",
  "assembled_url": "https://...",
  "assembled_url_expires_at": "2025-03-16T10:30:00Z",
  "project_url": "https://eversince.ai/app/projects/...",
  "agent_message": "Your project is complete...",
  "assets": [
    {
      "ref": "a1b2c3d4",
      "type": "image-to-video",
      "url": "https://...",
      "model": "wan-2.6",
      "prompt": "Opening shot of the product on a clean background",
      "duration": 5.0,
      "aspect_ratio": "16:9",
      "source_image_url": "https://...",
      "source_video_url": null,
      "reference_image_url": null,
      "created_at": "2025-03-15T10:32:00Z"
    }
  ],
  "timeline": {
    "duration_seconds": 15.0,
    "aspect_ratio": "16:9",
    "skills": ["skill-cinema"],
    "scenes": [
      {
        "id": "scene_uuid",
        "ref": "a1b2c3d4",
        "type": "video",
        "position": 1,
        "duration": 5,
        "description": "Opening shot",
        "image_url": "https://...",
        "video_url": "https://...",
        "video_model": "wan-2.6",
        "image_model": "flux-2-max",
        "prompt": "Opening shot of the product on a clean background",
        "volume": 1.0,
        "fade_in": null,
        "fade_out": null
      }
    ],
    "audio": {
      "voiceover": null,
      "music": null,
      "tracks": []
    },
    "overlays": [],
    "captions": null
  },
  "created_at": "2025-03-15T10:30:00Z",
  "updated_at": "2025-03-15T10:35:00Z"
}
```

## Signature verification

Webhook signing is optional. When a signing secret is configured for your account, every webhook includes signature headers for verification:

| Header                  | Description                |
| ----------------------- | -------------------------- |
| `X-Eversince-Signature` | `sha256=<hmac hex digest>` |
| `X-Eversince-Timestamp` | Unix timestamp (seconds)   |

### Verification

The signature is an HMAC-SHA256 of `{timestamp}.{raw body}` using your signing secret.

<CodeGroup>
  ```python Python theme={"dark"}
  import hmac
  import hashlib

  def verify_webhook(request, signing_secret):
      timestamp = request.headers["X-Eversince-Timestamp"]
      signature = request.headers["X-Eversince-Signature"]
      body = request.body.decode("utf-8")

      signed_content = f"{timestamp}.{body}"
      expected = "sha256=" + hmac.new(
          signing_secret.encode(),
          signed_content.encode(),
          hashlib.sha256
      ).hexdigest()

      return hmac.compare_digest(signature, expected)
  ```

  ```javascript JavaScript theme={"dark"}
  const crypto = require("crypto");

  function verifyWebhook(req, signingSecret) {
    const timestamp = req.headers["x-eversince-timestamp"];
    const signature = req.headers["x-eversince-signature"];
    const body = req.body; // raw string

    const signedContent = `${timestamp}.${body}`;
    const expected =
      "sha256=" +
      crypto.createHmac("sha256", signingSecret).update(signedContent).digest("hex");

    return crypto.timingSafeEqual(
      Buffer.from(signature),
      Buffer.from(expected)
    );
  }
  ```
</CodeGroup>

## Delivery

* **Timeout:** 10 seconds. Your endpoint must respond within this window.
* **Method:** POST with `Content-Type: application/json`
* Webhooks are delivered once. For reliability, confirm state with a `GET /projects/:id` call after receiving a webhook
