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

# Webhooks

> Receive real-time notifications when events happen.

## Overview

Webhooks let your server receive HTTP POST callbacks when events occur in SmartlyQ — like a job completing or a social post being published.

## Setting up webhooks

1. Go to the [Developer Dashboard](https://app.smartlyq.com/my/developer)
2. Navigate to the **Webhooks** tab
3. Add an endpoint URL (must be HTTPS)
4. Select the events you want to receive

## Payload format

All webhook payloads are JSON:

```json theme={null}
{
  "event": "job.completed",
  "timestamp": "2026-03-02T14:30:00Z",
  "data": {
    "job_id": "job_abc123",
    "type": "article_generation",
    "status": "completed",
    "result": { ... }
  }
}
```

## Verifying signatures

Every delivery is signed. Two headers accompany each request:

* `X-SmartlyQ-Signature` — `t=<timestamp>,v1=<hmac>`, where `<hmac>` is the HMAC-SHA256 of `<timestamp>.<raw-body>` keyed with your webhook secret.
* `X-SmartlyQ-Event` — the event name (e.g. `job.completed`).

Compute the HMAC over the timestamp and the **raw request body** joined with a `.`, then compare it against `v1`:

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

// rawBody = the exact bytes of the request body (do not re-serialize the JSON)
function verifySignature(rawBody, signatureHeader, secret) {
  const parts = Object.fromEntries(
    signatureHeader.split(",").map((kv) => kv.split("="))
  );
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${parts.t}.${rawBody}`)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(parts.v1),
    Buffer.from(expected)
  );
}
```

<Warning>
  Always verify the signature before processing a webhook. Unverified payloads may be spoofed. The signing secret is shown once, when you add the endpoint in the dashboard.
</Warning>

## Retry policy

If your endpoint returns a non-2xx status code, SmartlyQ retries delivery up to **5 times** with exponential backoff — the wait doubles each attempt (≈2, 4, 8, 16, 32 minutes). Deliveries still failing after the final attempt are dropped.

## Available events

| Event             | Trigger                                |
| ----------------- | -------------------------------------- |
| `job.completed`   | An async job finished successfully     |
| `job.failed`      | An async job failed                    |
| `social.posted`   | A social media post was published      |
| `social.failed`   | A social media post failed to publish  |
| `chatbot.message` | A chatbot received a visitor message   |
| `wallet.low`      | Wallet balance dropped below threshold |
