Webhooks
RedPennon can POST a JSON notification to your own HTTPS endpoint when something changes in your organisation. Add endpoints under Settings → Webhooks and choose which events each one receives.
Events
| Event | Sent when |
|---|---|
feature.created | A feature is created |
feature.updated | A feature's name, status, description or other settings change |
feature.archived | A feature's status changes to archived (also sent with feature.updated) |
feature.deleted | A feature is deleted |
variation.changed | The variation an environment serves for a variable changes |
targeting.updated | Targeting, a rule, a split or a condition changes in any environment |
member.joined | Someone joins the organisation |
member.removed | Someone leaves or is removed |
Request
POST /your/path HTTP/1.1
Content-Type: application/json
User-Agent: RedPennon-Webhooks/1
X-RedPennon-Event: variation.changed
X-RedPennon-Delivery: 6f1c…-…
X-RedPennon-Timestamp: 1792324800
X-RedPennon-Signature: v1=5d2a…
{
"id": "6f1c…",
"type": "variation.changed",
"created": "2026-10-18T09:20:00+00:00",
"organisation": "acme",
"data": {
"feature": {"key": "dark-mode", "name": "Dark mode", "project": "web", "status": "live"},
"environment": "production",
"variable": "dark-mode",
"variation": "on",
"actor": "jo@acme.com"
}
}
id matches X-RedPennon-Delivery and stays the same across retries, so use it to ignore duplicates. Member events carry data.member (email, role) instead of data.feature.
Verifying the signature
Each endpoint has a signing secret (whsec_…) shown in settings. The signature is an HMAC-SHA256 of the timestamp, a dot, and the raw request body:
import hashlib, hmac, time
def verify(secret: str, timestamp: str, body: bytes, header: str) -> bool:
if abs(time.time() - int(timestamp)) > 300:
return False # reject replays older than five minutes
expected = hmac.new(secret.encode(), f"{timestamp}.".encode() + body, hashlib.sha256).hexdigest()
return hmac.compare_digest(header, f"v1={expected}")
Verify against the raw body bytes, before parsing the JSON. Rotating the secret in settings takes effect for the next delivery.
Delivery and retries
- Respond with any
2xxwithin 5 seconds. Redirects are not followed. - Any other answer, a timeout, or a connection error is retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 12 hours, then the delivery is marked failed.
- The endpoint's last delivery time and result are shown in settings.
- Endpoints must resolve to public internet addresses. Private, loopback and link-local addresses are refused, both when you save the endpoint and on every send.
- Deliveries are sent in the order they are due, roughly once a minute; ordering between events is not guaranteed.