> For the complete documentation index, see [llms.txt](https://docs.viesus.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.viesus.com/reference/cloud-api/webhooks.md).

# Webhooks

Webhooks deliver push notifications to your server when an enhancement completes (or fails). This eliminates the need to poll the API and is the recommended pattern for production integrations.

Up to **5 webhooks** can be registered per account. Webhooks fire only for enhancements triggered via the API — not for enhancements run through the dashboard UI.

***

## Create a webhook

```graphql
mutation {
  createWebhook(input: {
    url: "https://your-server.com/webhooks/viesus"
  }) {
    id
    url
    secret
    createdAt
  }
}
```

Save the returned `secret` immediately — it is used to verify incoming requests and is **shown only once**. If lost, delete the webhook and create a new one.

***

## List webhooks

```graphql
query {
  webhooks {
    id
    url
    createdAt
  }
}
```

## Retrieve a single webhook

```graphql
query {
  webhook(id: "webhook-id") {
    id
    url
    secret
    createdAt
  }
}
```

***

## Delete a webhook

```graphql
mutation {
  deleteWebhook(id: "webhook-id") {
    success
  }
}
```

***

## Webhook payload

When an enhancement finishes, VIESUS Cloud sends a `POST` request to your URL with a JSON body:

```json
{
  "id": "eventId",
  "event": "upload.sub_status_update",
  "data": {
    "id": "uploadId",
    "status": "UPLOAD_FINISHED",
    "subStatus": "METADATA_ANALYZED_SUCCESSFULLY",
    "filesize": 297400,
    "mimetype": "application/pdf",
    "originalFilename": "photobook.pdf",
    "filename": "photobook.pdf",
    "startedAt": "2025-05-15T00:00:00.000Z",
    "finishedAt": "2025-05-15T00:00:12.000Z",
    "duration": 12000,
    "metadata": {
      "statistics": {},
      "totalCredits": 78
    }
  },
  "created": "2025-05-15T14:12:37.501Z"
}
```

Your endpoint must return a `2xx` status code to acknowledge delivery. Non-2xx responses trigger retries.

***

## Verifying webhook signatures

Every incoming webhook request includes an `x-viesus-cloud-signature` header containing an **HMAC-SHA256** hash of the raw request body, signed with your webhook `secret`.

Always verify this signature before processing the payload to ensure the request came from VIESUS Cloud.

**Node.js (Express):**

```js
const crypto = require('crypto');
const express = require('express');

const app = express();

// Use raw body buffer for signature verification
app.use('/webhooks/viesus', express.raw({ type: 'application/json' }));

app.post('/webhooks/viesus', (req, res) => {
  const signature = req.headers['x-viesus-cloud-signature'];
  const secret = process.env.VIESUS_WEBHOOK_SECRET;

  const expected = crypto
    .createHmac('sha256', secret)
    .update(req.body)
    .digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
    return res.status(401).send('Invalid signature');
  }

  const event = JSON.parse(req.body);
  console.log('Received event:', event.event, event.data.id);

  // Process the event...

  res.sendStatus(200);
});
```

{% hint style="danger" %}
Compute the HMAC over the **raw request body bytes**, not a re-serialized JSON object. Any whitespace or key ordering difference produces a different hash and breaks verification.
{% endhint %}

**Python (Flask):**

```python
import hmac
import hashlib
import os
from flask import Flask, request, abort

app = Flask(__name__)

@app.route('/webhooks/viesus', methods=['POST'])
def webhook():
    secret = os.environ['VIESUS_WEBHOOK_SECRET'].encode()
    signature = request.headers.get('x-viesus-cloud-signature', '')
    expected = hmac.new(secret, request.get_data(), hashlib.sha256).hexdigest()

    if not hmac.compare_digest(expected, signature):
        abort(401)

    event = request.get_json()
    print(f"Received: {event['event']} for {event['data']['id']}")
    return '', 200
```

A full Node.js example is available at: <https://github.com/Viesus-Cloud/webhooks-node-example>

***

## Retry behavior

If your endpoint does not return a `2xx` response within the timeout, VIESUS Cloud retries delivery **9 times** with a **1-hour delay** between attempts. After 9 failures the event is marked as permanently failed.

Design your endpoint to be idempotent — it may receive the same event multiple times.

***

## Webhook logs

Inspect delivery history for debugging failed webhooks:

```graphql
query {
  webhookLogs(
    webhookId: "webhook-id"
    filter: {
      take: 25
      skip: 0
    }
  ) {
    items {
      id
      webhookData
      responseBody
      responseStatusCode
      status
      createdAt
    }
  }
}
```

Filter by outcome:

```graphql
filter: { take: 25, skip: 0, status: FAILED }
```

***

## Events reference

The Cloud API documents a single webhook event:

| Event                      | When it fires                                                                                                                                               | Key fields                                                     |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| `upload.sub_status_update` | An upload's status / sub-status changes — e.g. PDF analysis progress (`METADATA_ANALYZING` → `METADATA_ANALYZED_SUCCESSFULLY` / `METADATA_ANALYZED_FAILED`) | `data.status`, `data.subStatus` — branch your handler on these |

To check whether an **enhancement** has finished, query `enhancedImage { status fullUrl errorCode }` — `status` is `QUEUED`, `FINISHED`, or `ERROR`. The API does not document a separate webhook event name for enhancement completion.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.viesus.com/reference/cloud-api/webhooks.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
