Webhook Signatures
PromptJang signs every outbound webhook delivery with HMAC-SHA256. This lets your target endpoint verify that the payload came through PromptJang and wasn't tampered with.
How It Works
When PromptJang delivers a webhook to your target endpoint, we add these headers:
| Header | Description |
|---|---|
X-PromptJang-Signature | HMAC-SHA256 hex digest of timestamp.raw_body |
X-PromptJang-Timestamp | Unix timestamp (seconds) when the delivery was made |
X-PromptJang-Event-ID | Unique event ID — use for idempotency |
X-PromptJang-Event-Type | Event type (if set) |
X-Correlation-ID | Your correlation ID (if provided when sending) |
Signing Algorithm
timestamp = current_unix_seconds()
signature = HMAC-SHA256(endpoint_secret, "${timestamp}.${raw_body}")The endpoint secret is generated when you create an endpoint and returned in the response.
Verifying on Your Target Endpoint
import hmac, hashlib, time
def verify_webhook(request):
signature = request.headers['X-PromptJang-Signature']
timestamp = request.headers['X-PromptJang-Timestamp']
body = request.raw_body
# 1. Check timestamp freshness (reject stale deliveries)
if abs(time.time() - int(timestamp)) > 300:
return 401, "Stale timestamp"
# 2. Reconstruct the signed content
signed_content = f"{timestamp}.".encode() + body
# 3. Compute expected signature
expected = hmac.new(
ENDPOINT_SECRET.encode(),
signed_content,
hashlib.sha256
).hexdigest()
# 4. Compare (constant-time)
if not hmac.compare_digest(expected, signature):
return 401, "Invalid signature"
# 5. Process the event
event_id = request.headers['X-PromptJang-Event-ID']
if already_processed(event_id):
return 200 # Already handled — idempotent
process_event(event_id, body)
return 200Idempotency
Every delivery includes X-PromptJang-Event-ID. Your target endpoint should use this to avoid processing the same event twice:
- Retries: If we retry and your endpoint already processed the event, return 200
- Network blips: If your server processed it but the connection dropped before we got the response
- Replay: Manual replays get a new event ID, so they're treated as new events
Optional: Inbound Signature Verification
By default, sending events to PromptJang only requires an API key. For security-sensitive integrations, PromptJang can be configured to require HMAC signatures on inbound events too (the same algorithm). Contact support to enable this.
When enabled, you must include X-PromptJang-Signature and X-PromptJang-Timestamp headers when sending events to POST /e/:endpoint_id, signing with the same endpoint secret.