Webhooks
Webhooks are available to Reseller and Agency accounts only.
Webhooks allow you to receive real-time HTTP notifications when license events occur. Instead of polling the API, your server is notified instantly.
Supported Events
| Event | Triggered When |
|---|---|
license.created | A new license is created |
license.activated | A license is activated on a domain |
license.deactivated | A license is deactivated from a domain |
license.revoked | A license is revoked |
license.expired | A license expires (e.g., subscription expiration) |
Setting Up a Webhook
Via Portal
- Go to Webhooks in your portal
- Click Create Webhook
- Enter:
- URL — your HTTPS endpoint that will receive the webhook
- Events — select which events to subscribe to
- Description (optional) — for your own reference
- Click Create
A unique HMAC secret is automatically generated for signature verification.
Via API
curl -X POST https://admin.flavorteam.dev/api/v1/portal/webhooks \
-H "Authorization: Bearer eyJ..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-server.com/webhooks/flavor",
"events": ["license.created", "license.activated"],
"description": "Production webhook"
}'
Response:
{
"id": 1,
"url": "https://your-server.com/webhooks/flavor",
"events": ["license.created", "license.activated"],
"secret": "a1b2c3d4...64 hex chars...",
"is_active": true,
"description": "Production webhook",
"created_at": "2026-03-30T10:00:00Z"
}
The secret is shown at creation time. Store it securely — you'll need it to verify webhook signatures.
Webhook Payload
When an event occurs, Flavor Hub sends an HTTP POST to your URL:
POST /webhooks/flavor HTTP/1.1
Host: your-server.com
Content-Type: application/json
User-Agent: FlavorHub-Webhook/1.0
X-Hub-Event: license.created
X-Hub-Signature-256: sha256=abc123...
{
"event": "license.created",
"timestamp": "2026-03-30T12:00:00+00:00",
"data": {
"license_id": 42,
"license_key": "FLVR-AB12...",
"tier": "starter",
"product_slug": "flavor-starter",
"expires_at": "2027-03-30T12:00:00+00:00"
}
}
Event Data Fields
license.created
{
"license_id": 42,
"license_key": "FLVR-AB12...",
"tier": "starter",
"product_slug": "flavor-starter",
"expires_at": "2027-03-30T12:00:00+00:00"
}
license.activated
{
"license_key": "FLVR-AB12...",
"domain": "client-site.com",
"tier": "starter",
"product_slug": "flavor-starter"
}
license.deactivated
{
"license_key": "FLVR-AB12...",
"domain": "client-site.com"
}
license.revoked
{
"license_key": "FLVR-AB12...",
"deactivated_domains": ["site1.com", "site2.com"]
}
license.expired
{
"customer_id": 15,
"licenses_revoked": 3,
"subscription_id": 8
}
Verifying Signatures
Every webhook request includes an X-Hub-Signature-256 header containing an HMAC-SHA256 signature of the request body, signed with your webhook's secret.
Always verify this signature to ensure the request came from Flavor Hub.
Verification Example (Node.js)
const crypto = require('crypto');
function verifyWebhook(payload, signature, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// In your Express handler:
app.post('/webhooks/flavor', (req, res) => {
const signature = req.headers['x-hub-signature-256'];
const isValid = verifyWebhook(
JSON.stringify(req.body),
signature,
process.env.WEBHOOK_SECRET
);
if (!isValid) {
return res.status(401).send('Invalid signature');
}
// Process the webhook...
console.log('Event:', req.body.event);
console.log('Data:', req.body.data);
res.status(200).send('OK');
});
Verification Example (PHP)
function verifyWebhook(string $payload, string $signature, string $secret): bool {
$expected = 'sha256=' . hash_hmac('sha256', $payload, $secret);
return hash_equals($expected, $signature);
}
// In your webhook handler:
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_HUB_SIGNATURE_256'] ?? '';
if (!verifyWebhook($payload, $signature, $webhookSecret)) {
http_response_code(401);
exit('Invalid signature');
}
$event = json_decode($payload, true);
// Process $event['event'] and $event['data']...
http_response_code(200);
echo 'OK';
Verification Example (Python)
import hmac
import hashlib
def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
expected = 'sha256=' + hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
# In your Flask/FastAPI handler:
@app.post("/webhooks/flavor")
async def handle_webhook(request: Request):
payload = await request.body()
signature = request.headers.get("X-Hub-Signature-256", "")
if not verify_webhook(payload, signature, WEBHOOK_SECRET):
raise HTTPException(status_code=401, detail="Invalid signature")
event = await request.json()
# Process event["event"] and event["data"]...
return {"status": "ok"}
Testing Webhooks
Use the Test button on any webhook to send a test ping:
curl -X POST https://admin.flavorteam.dev/api/v1/portal/webhooks/{id}/test \
-H "Authorization: Bearer eyJ..."
The test sends a ping to your URL and reports:
- Success — your endpoint responded with 2xx
- Status code — the HTTP response code
- Error — any error message if the delivery failed
Delivery Logs
View recent deliveries for any webhook:
curl "https://admin.flavorteam.dev/api/v1/portal/webhooks/{id}/deliveries?limit=25" \
-H "Authorization: Bearer eyJ..."
Each delivery log shows:
- Event type — which event triggered the delivery
- Payload — the full JSON sent
- Status code — HTTP response from your server
- Success — whether the delivery was successful (2xx)
- Error — error message if failed
- Timestamp
Up to 100 recent deliveries are stored per webhook.
Managing Webhooks
Update
curl -X PATCH https://admin.flavorteam.dev/api/v1/portal/webhooks/{id} \
-H "Authorization: Bearer eyJ..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://new-url.com/webhook",
"events": ["license.created"],
"is_active": false
}'
Delete
curl -X DELETE https://admin.flavorteam.dev/api/v1/portal/webhooks/{id} \
-H "Authorization: Bearer eyJ..."
Deleting a webhook also removes all its delivery logs.
Limits
- Maximum 10 webhooks per account
- Deliveries have a 10-second timeout — ensure your endpoint responds quickly
- Return a 2xx status code to acknowledge receipt
- Up to 100 delivery logs stored per webhook
Best Practices
- Always verify signatures — never trust unverified webhook payloads
- Respond quickly (within 5 seconds) — process asynchronously if needed
- Return 200 immediately, then process the event in the background
- Handle duplicates — webhooks may be delivered more than once in edge cases
- Use HTTPS — webhook URLs should always use HTTPS
- Monitor delivery logs — check for failures regularly in the portal