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

# Feed Webhooks

> Subscribe to feed lifecycle events and verify HMAC-SHA256 signatures.

## Overview

Feed webhooks notify your server when feed generation completes, fails, or validation finishes. Configure one or more webhook endpoints per workspace.

## Webhook Events

| Event            | Trigger                                |
| ---------------- | -------------------------------------- |
| `feed.generated` | Feed generation completed successfully |
| `feed.failed`    | Feed generation failed                 |
| `feed.validated` | Feed validation check completed        |

## Create a Subscription

```
POST /api/v1/feeds/{platform}/webhooks
```

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://app.alana.shopping/api/v1/feeds/google/webhooks" \
    -H "X-API-Key: sk_live_your_api_key" \
    -H "Content-Type: application/json" \
    -d '{
      "url": "https://yourserver.com/webhooks/alana",
      "events": ["feed.generated", "feed.failed"],
      "secret": "your_webhook_secret"
    }'
  ```

  ```javascript JavaScript theme={null}
  const res = await fetch(
    "https://app.alana.shopping/api/v1/feeds/google/webhooks",
    {
      method: "POST",
      headers: {
        "X-API-Key": "sk_live_your_api_key",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        url: "https://yourserver.com/webhooks/alana",
        events: ["feed.generated", "feed.failed"],
        secret: "your_webhook_secret",
      }),
    }
  );
  const subscription = await res.json();
  console.log(subscription.id);
  ```

  ```python Python theme={null}
  import requests

  res = requests.post(
      "https://app.alana.shopping/api/v1/feeds/google/webhooks",
      headers={
          "X-API-Key": "sk_live_your_api_key",
          "Content-Type": "application/json",
      },
      json={
          "url": "https://yourserver.com/webhooks/alana",
          "events": ["feed.generated", "feed.failed"],
          "secret": "your_webhook_secret",
      },
  )
  subscription = res.json()
  print(subscription["id"])
  ```
</CodeGroup>

## Webhook Payload

```json theme={null}
{
  "event": "feed.generated",
  "workspace_id": "ws_abc123",
  "platform": "google",
  "feed_url": "https://app.alana.shopping/api/v1/feeds/google",
  "product_count": 1432,
  "generated_at": "2026-03-17T12:00:00Z"
}
```

## Verifying Signatures (HMAC-SHA256)

Every webhook request includes an `X-Alana-Signature` header. Verify it to confirm the request came from Alana:

<CodeGroup>
  ```bash cURL theme={null}
  # Signature is sent in the X-Alana-Signature header
  # X-Alana-Signature: sha256=abc123...
  ```

  ```javascript JavaScript theme={null}
  import crypto from "crypto";

  function verifySignature(payload, signature, secret) {
    const expected = crypto
      .createHmac("sha256", secret)
      .update(payload, "utf8")
      .digest("hex");
    const received = signature.replace("sha256=", "");
    return crypto.timingSafeEqual(
      Buffer.from(expected, "hex"),
      Buffer.from(received, "hex")
    );
  }

  // In your webhook handler (Express example):
  app.post("/webhooks/alana", (req, res) => {
    const signature = req.headers["x-alana-signature"];
    const isValid = verifySignature(
      JSON.stringify(req.body),
      signature,
      process.env.WEBHOOK_SECRET
    );

    if (!isValid) return res.status(401).send("Invalid signature");

    const { event, platform, product_count } = req.body;
    console.log(`Feed ${event} for ${platform}: ${product_count} products`);
    res.sendStatus(200);
  });
  ```

  ```python Python theme={null}
  import hmac
  import hashlib

  def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
      expected = hmac.new(
          secret.encode(), payload, hashlib.sha256
      ).hexdigest()
      received = signature.replace("sha256=", "")
      return hmac.compare_digest(expected, received)

  # Flask example:
  from flask import Flask, request, abort
  import json

  app = Flask(__name__)

  @app.post("/webhooks/alana")
  def handle_webhook():
      signature = request.headers.get("X-Alana-Signature", "")
      if not verify_signature(request.data, signature, WEBHOOK_SECRET):
          abort(401)

      data = request.json
      print(f"Feed {data['event']} for {data['platform']}")
      return "", 200
  ```
</CodeGroup>

## List Subscriptions

```
GET /api/v1/feeds/{platform}/webhooks
```

## Delete a Subscription

```
DELETE /api/v1/feeds/{platform}/webhooks/{webhook_id}
```

## Retry Policy

Alana retries failed webhook deliveries with exponential backoff: 1 min, 5 min, 30 min, 2 h, 8 h. After 5 failed attempts, the subscription is marked inactive and must be re-enabled manually.

Respond with any `2xx` status within 10 seconds to acknowledge receipt.
