Webhooks
Subscribe to real-time notifications for new violations, enforcement actions, and compliance status changes. Requires Pro tier.
Endpoints
/v1/webhooksPro+Create a new webhook subscription.
/v1/webhooksPro+List all your webhook subscriptions.
/v1/webhooks/{webhook_id}Pro+Get details for a specific webhook (secret is redacted).
/v1/webhooks/{webhook_id}Pro+Update a webhook's URL, events, facility list, or active status.
/v1/webhooks/{webhook_id}Pro+Delete a webhook subscription.
Event Types
| Event | Description |
|---|---|
violation.new | A new violation is recorded for a watched facility |
case.new | A new enforcement case is filed involving a watched facility |
case.updated | A case status changes (e.g., settled, closed) |
facility.status_changed | A facility's compliance status changes |
Create a webhook
/v1/webhooksRequest Body (JSON)
| Parameter | Type | Description |
|---|---|---|
url* | string | HTTPS URL to deliver webhook payloads toe.g. https://myapp.com/webhooks/parley |
events* | string[] | Array of event types to subscribe to |
facility_ids | string[] | Array of facility UUIDs to watch. Omit to watch all facilities. |
urlrequiredstringHTTPS URL to deliver webhook payloads to
e.g. https://myapp.com/webhooks/parleyeventsrequiredstring[]Array of event types to subscribe to
facility_idsstring[]Array of facility UUIDs to watch. Omit to watch all facilities.
curl -X POST "https://api.parley.dev/v1/webhooks" \ -H "X-API-Key: prl_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "url": "https://myapp.com/webhooks/parley", "events": ["violation.new", "case.new"], "facility_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"] }'{ "data": { "id": "wh_a1b2c3d4-...", "url": "https://myapp.com/webhooks/parley", "events": ["violation.new", "case.new"], "facility_ids": ["f47ac10b-..."], "secret": "whsec_9f8e7d6c5b4a3210...", "is_active": true, "created_at": "2026-03-20T12:00:00Z" }}Important: The secret field is only returned once, at creation time. Store it securely for verifying webhook signatures.
Delivery Payload
When an event matches your subscription, Parley sends an HTTP POST to your webhook URL with the following payload:
{ "id": "evt_a1b2c3d4", "type": "violation.new", "created_at": "2026-03-20T12:00:00Z", "data": { "violation": { "id": "a1b2c3d4-...", "facility_id": "f47ac10b-...", "facility_name": "ACME CHEMICAL PLANT", "program": "CAA", "violation_type": "High Priority Violation", "violation_date": "2025-11-22", "severity": "HIGH" } }}Verifying Signatures
Every webhook delivery includes an X-Parley-Signature header containing an HMAC-SHA256 signature of the raw request body.
X-Parley-Signature: sha256=5d7226c3c7a2...Verification example (Node.js)
const crypto = require("crypto");function verifySignature(secret, body, signature) { const expected = "sha256=" + crypto.createHmac("sha256", secret) .update(body) .digest("hex"); return crypto.timingSafeEqual( Buffer.from(expected), Buffer.from(signature) );}// In your Express handler:app.post("/webhooks/parley", (req, res) => { const sig = req.headers["x-parley-signature"]; const valid = verifySignature(WEBHOOK_SECRET, req.rawBody, sig); if (!valid) return res.status(401).send("Invalid signature"); const event = req.body; console.log(event.type, event.data); res.status(200).send("ok");});Verification example (Python)
import hmacimport hashlibdef verify_signature(secret: str, body: bytes, signature: str) -> bool: expected = "sha256=" + hmac.new( secret.encode(), body, hashlib.sha256 ).hexdigest() return hmac.compare_digest(expected, signature)# In your Flask handler:@app.route("/webhooks/parley", methods=["POST"])def handle_webhook(): sig = request.headers.get("X-Parley-Signature") if not verify_signature(WEBHOOK_SECRET, request.data, sig): return "Invalid signature", 401 event = request.json print(event["type"], event["data"]) return "ok", 200Retry Policy
If your endpoint returns a non-2xx status code, Parley will retry delivery with exponential backoff:
| Attempt | Delay |
|---|---|
| Retry 1 | 1 minute |
| Retry 2 | 5 minutes |
| Retry 3 | 30 minutes |
| Retry 4 | 2 hours |
| Retry 5 | 12 hours |
After 5 consecutive failures, the webhook is automatically deactivated. You can reactivate it by sending a PATCH request with "is_active": true.