Webhooks

Receive real-time notifications when events occur in Keypost.

Setup

  1. Go to your team settings in the dashboard
  2. Click Webhooks
  3. Add your endpoint URL
  4. Select which events to receive
  5. Copy the signing secret

Payload format

{
  "id": "evt_abc123",
  "type": "policy.denied",
  "created_at": "2024-01-15T10:30:00Z",
  "data": {
    "keypost_id": "kp_xyz789",
    "keypost_slug": "my-server",
    "tool_name": "delete_user",
    "policy_name": "No destructive ops",
    "reason": "Tool blocked by access policy"
  }
}

Event types

policy.denied

Fired when a request is blocked by a policy.

{
  "type": "policy.denied",
  "data": {
    "keypost_id": "kp_xyz789",
    "keypost_slug": "my-server",
    "tool_name": "delete_user",
    "policy_name": "No destructive ops",
    "policy_type": "access",
    "reason": "Tool blocked by access policy",
    "request_params": { ... }
  }
}

approval.requested

Fired when a new approval request is created.

{
  "type": "approval.requested",
  "data": {
    "approval_id": "apr_abc123",
    "keypost_slug": "my-server",
    "tool_name": "transfer_funds",
    "policy_name": "Large transfers",
    "approvers": ["finance@company.com"],
    "expires_at": "2024-01-16T10:30:00Z"
  }
}

approval.decided

Fired when an approval is approved or denied.

{
  "type": "approval.decided",
  "data": {
    "approval_id": "apr_abc123",
    "decision": "approved",
    "decided_by": "admin@company.com",
    "note": "Verified with customer"
  }
}

circuit_breaker.opened

Fired when a circuit breaker opens due to upstream failures.

{
  "type": "circuit_breaker.opened",
  "data": {
    "keypost_slug": "my-server",
    "failure_count": 5,
    "last_error": "Connection refused"
  }
}

circuit_breaker.closed

Fired when a circuit breaker recovers.

{
  "type": "circuit_breaker.closed",
  "data": {
    "keypost_slug": "my-server",
    "recovery_time_seconds": 120
  }
}

Verifying signatures

Each webhook includes a signature header for verification:

X-Keypost-Signature: sha256=abc123...

Verify by computing HMAC-SHA256 of the raw body using your signing secret:

const crypto = require('crypto');

function verifySignature(payload, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  return `sha256=${expected}` === signature;
}

Retry behavior

If your endpoint returns a non-2xx status, Keypost retries with exponential backoff:

  • 1st retry: 1 minute
  • 2nd retry: 5 minutes
  • 3rd retry: 30 minutes
  • 4th retry: 2 hours
  • 5th retry: 24 hours

After 5 failed attempts, the event is dropped and logged.