Skip to main content

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​

EventSent when
feature.createdA feature is created
feature.updatedA feature's name, status, description or other settings change
feature.archivedA feature's status changes to archived (also sent with feature.updated)
feature.deletedA feature is deleted
variation.changedThe variation an environment serves for a variable changes
targeting.updatedTargeting, a rule, a split or a condition changes in any environment
member.joinedSomeone joins the organisation
member.removedSomeone 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 2xx within 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.