Skip to Content
APIWebhooks

Webhooks

Webhooks allow your application to receive real-time notifications when events occur in the Curate-Me platform. Instead of polling the API, you register a URL and the platform sends HTTP POST requests to it when relevant events fire.

Webhook Events

EventDescription
analysis.completeAn analysis pipeline has finished (success or failure)
subscription.createdA new subscription was created
subscription.updatedA subscription plan was changed
subscription.cancelledA subscription was cancelled
price.alertA tracked product price dropped below the alert threshold

Setting Up Webhooks

Register a webhook endpoint through the B2B API.

POST /api/v1/admin/webhooks

Headers:

Authorization: Bearer {token} X-Org-ID: {organization_id} Content-Type: application/json

Request:

{ "url": "https://your-app.com/webhooks/curate-me", "events": ["analysis.complete", "price.alert"], "secret": "whsec_your_signing_secret" }

Response (201):

{ "id": "wh_abc123", "url": "https://your-app.com/webhooks/curate-me", "events": ["analysis.complete", "price.alert"], "status": "active", "created_at": "2026-02-08T15:00:00Z" }

Webhook Payload

Each webhook delivery is an HTTP POST request with a JSON body and signature headers.

Headers

HeaderDescription
X-Webhook-SignatureHMAC-SHA256 signature of the request body
X-Webhook-EventThe event type (e.g., analysis.complete)
X-Webhook-TimestampUnix timestamp of when the event was generated
Content-TypeAlways application/json

Example Payload

{ "id": "evt_xyz789", "event": "analysis.complete", "timestamp": "2026-02-08T14:23:00Z", "data": { "analysis_id": "pipe_abc123", "status": "completed", "total_cost": 0.0142, "total_latency_ms": 4500, "result_url": "https://api.curate-me.ai/api/v1/analyze/pipe_abc123" } }

Signature Verification

Every webhook request is signed using HMAC-SHA256 with the secret you provided when creating the webhook. Always verify the signature before processing the payload.

The signature is computed over the raw request body using the webhook secret as the HMAC key.

Python

import hmac import hashlib def verify_webhook(payload: bytes, signature: str, secret: str) -> bool: """Verify the webhook signature. Args: payload: Raw request body bytes. signature: Value of the X-Webhook-Signature header. secret: The webhook signing secret (whsec_...). Returns: True if the signature is valid. """ expected = hmac.new( secret.encode("utf-8"), payload, hashlib.sha256, ).hexdigest() return hmac.compare_digest(f"sha256={expected}", signature) # Usage in a FastAPI route: from fastapi import Request, HTTPException @app.post("/webhooks/curate-me") async def handle_webhook(request: Request): body = await request.body() signature = request.headers.get("X-Webhook-Signature", "") if not verify_webhook(body, signature, WEBHOOK_SECRET): raise HTTPException(status_code=401, detail="Invalid signature") event = request.headers.get("X-Webhook-Event") data = await request.json() match event: case "analysis.complete": await handle_analysis_complete(data["data"]) case "price.alert": await handle_price_alert(data["data"]) return {"status": "ok"}

JavaScript / Node.js

const crypto = require('crypto'); function verifyWebhook(payload, signature, secret) { const expected = crypto .createHmac('sha256', secret) .update(payload, 'utf8') .digest('hex'); const expectedSignature = `sha256=${expected}`; return crypto.timingSafeEqual( Buffer.from(expectedSignature), Buffer.from(signature) ); } // Usage in an Express route: const express = require('express'); const app = express(); app.post('/webhooks/curate-me', express.raw({ type: 'application/json' }), (req, res) => { const signature = req.headers['x-webhook-signature']; const event = req.headers['x-webhook-event']; if (!verifyWebhook(req.body, signature, WEBHOOK_SECRET)) { return res.status(401).json({ error: 'Invalid signature' }); } const data = JSON.parse(req.body); switch (event) { case 'analysis.complete': handleAnalysisComplete(data.data); break; case 'price.alert': handlePriceAlert(data.data); break; } res.json({ status: 'ok' }); });

Retry Policy

If your endpoint returns a non-2xx status code or the request times out (30 seconds), the platform retries the delivery with exponential backoff:

AttemptDelay
1st retry1 minute
2nd retry5 minutes
3rd retry30 minutes
4th retry2 hours
5th retry24 hours

After 5 failed retries, the delivery is marked as failed. Failed deliveries can be viewed and manually retried through the dashboard or API.

Managing Webhooks

List Webhooks

GET /api/v1/admin/webhooks

Update a Webhook

PATCH /api/v1/admin/webhooks/{id}
{ "events": ["analysis.complete", "price.alert", "subscription.created"], "url": "https://your-app.com/webhooks/v2/curate-me" }

Delete a Webhook

DELETE /api/v1/admin/webhooks/{id}

View Delivery History

GET /api/v1/admin/webhooks/{id}/deliveries?offset=0&limit=20

Returns recent delivery attempts including status codes, response times, and retry counts.