Webhooks API

Real-time event notification endpoints for subscribing to system events with automatic retries, signature verification, and delivery tracking.

Updated Nov 22, 2025
Edit on GitHub
webhooks events notifications integrations

Webhooks API Reference

Overview

The Webhooks API enables real-time event notifications for organization activities. Webhooks deliver HTTP POST requests to configured URLs when specific events occur, allowing seamless integration with external systems for monitoring, analytics, alerting, and workflow automation.

All webhook endpoints require organization owner or admin permissions unless otherwise noted.

Key Features

  • Real-time Event Delivery: Instant HTTP POST notifications when events occur
  • Automatic Retry Logic: Exponential backoff with up to 5 retry attempts for failed deliveries
  • Signature Verification: HMAC-SHA256 signatures for webhook authenticity verification
  • Delivery Tracking: Complete audit trail of webhook deliveries with response details
  • Event Filtering: Subscribe to specific event types or all events
  • Active/Inactive Toggle: Enable or disable webhooks without deletion

Endpoints Summary

Method Path Description
POST /api/organizations/:org_slug/webhooks Create a new webhook
GET /api/organizations/:org_slug/webhooks List all webhooks
GET /api/organizations/:org_slug/webhooks/:webhook_id Get webhook details
PATCH /api/organizations/:org_slug/webhooks/:webhook_id Update webhook configuration
DELETE /api/organizations/:org_slug/webhooks/:webhook_id Delete a webhook
GET /api/organizations/:org_slug/webhooks/:webhook_id/deliveries Get webhook delivery history
GET /api/organizations/:org_slug/webhooks/event-types List available event types

Webhook Management

POST /api/organizations/:org_slug/webhooks

Create a new webhook for an organization. The webhook secret is generated automatically and returned only once upon creation.

Authentication: Required (Organization Management JWT)

Permissions: Organization owner or admin

Path Parameters:

Parameter Type Description
org_slug string Organization slug

Request Body:

Field Type Required Description
name string Yes Webhook name (must be unique within organization)
url string Yes Target URL for webhook deliveries (must start with http:// or https://)
events array of strings Yes List of event types to subscribe to (at least one required)

Request Headers:

Authorization: Bearer {jwt_token}
Content-Type: application/json

Example Request:

curl -X POST https://sso.example.com/api/organizations/acme-corp/webhooks \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Production Webhook",
    "url": "https://api.acme.com/webhooks/sso",
    "events": [
      "user.login.success",
      "user.signup.success",
      "user.mfa.enabled"
    ]
  }'

Response (200 OK):

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "Production Webhook",
  "url": "https://api.acme.com/webhooks/sso",
  "events": [
    "user.login.success",
    "user.signup.success",
    "user.mfa.enabled"
  ],
  "is_active": true,
  "created_at": "2025-01-15T10:30:00Z",
  "updated_at": "2025-01-15T10:30:00Z"
}

Response Fields:

  • id (string): Webhook unique identifier
  • name (string): Webhook name
  • url (string): Target URL
  • events (array of strings): Subscribed event types
  • is_active (boolean): Whether webhook is active
  • created_at (string): ISO 8601 timestamp
  • updated_at (string): ISO 8601 timestamp

Error Responses:

  • 400 Bad Request: Invalid input (empty name/url, invalid URL format, invalid event types, duplicate name)
    {
      "error": "A webhook with this name already exists"
    }
    
    {
      "error": "Invalid event type: invalid.event. Valid types: user.login.success, ..."
    }
    
  • 401 Unauthorized: Missing or invalid JWT token
  • 403 Forbidden: User is not an admin or owner
  • 404 Not Found: Organization not found

Important Notes:

  • The webhook secret is generated automatically and must be stored securely for signature verification
  • Webhook names must be unique within the organization
  • At least one event type must be specified
  • Invalid event types will return an error with the list of valid event types

GET /api/organizations/:org_slug/webhooks

List all webhooks configured for an organization.

Authentication: Required (Organization Management JWT)

Permissions: Organization owner or admin

Path Parameters:

Parameter Type Description
org_slug string Organization slug

Query Parameters: None

Request Headers:

Authorization: Bearer {jwt_token}

Example Request:

curl -X GET https://sso.example.com/api/organizations/acme-corp/webhooks \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."

Response (200 OK):

{
  "webhooks": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "Production Webhook",
      "url": "https://api.acme.com/webhooks/sso",
      "events": ["user.login.success", "user.signup.success"],
      "is_active": true,
      "created_at": "2025-01-15T10:30:00Z",
      "updated_at": "2025-01-15T10:30:00Z"
    },
    {
      "id": "660e8400-e29b-41d4-a716-446655440111",
      "name": "Staging Webhook",
      "url": "https://staging-api.acme.com/webhooks/sso",
      "events": ["user.mfa.enabled", "user.mfa.disabled"],
      "is_active": false,
      "created_at": "2025-01-14T08:15:00Z",
      "updated_at": "2025-01-14T08:15:00Z"
    }
  ],
  "total": 2
}

Response Fields:

  • webhooks (array): List of webhook objects
  • total (integer): Total number of webhooks

Error Responses:

  • 401 Unauthorized: Missing or invalid JWT token
  • 403 Forbidden: User is not an admin or owner
  • 404 Not Found: Organization not found

GET /api/organizations/:org_slug/webhooks/:webhook_id

Get details for a specific webhook.

Authentication: Required (Organization Management JWT)

Permissions: Organization owner or admin

Path Parameters:

Parameter Type Description
org_slug string Organization slug
webhook_id string Webhook ID

Query Parameters: None

Request Headers:

Authorization: Bearer {jwt_token}

Example Request:

curl -X GET https://sso.example.com/api/organizations/acme-corp/webhooks/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."

Response (200 OK):

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "Production Webhook",
  "url": "https://api.acme.com/webhooks/sso",
  "events": ["user.login.success", "user.signup.success", "user.mfa.enabled"],
  "is_active": true,
  "created_at": "2025-01-15T10:30:00Z",
  "updated_at": "2025-01-15T10:30:00Z"
}

Error Responses:

  • 401 Unauthorized: Missing or invalid JWT token
  • 403 Forbidden: User is not an admin or owner
  • 404 Not Found: Organization or webhook not found

PATCH /api/organizations/:org_slug/webhooks/:webhook_id

Update webhook configuration. All fields are optional - only provided fields will be updated.

Authentication: Required (Organization Management JWT)

Permissions: Organization owner or admin

Path Parameters:

Parameter Type Description
org_slug string Organization slug
webhook_id string Webhook ID

Request Body:

Field Type Required Description
name string No New webhook name (must be unique)
url string No New target URL
events array of strings No New list of event types
is_active boolean No Enable or disable webhook

Request Headers:

Authorization: Bearer {jwt_token}
Content-Type: application/json

Example Request:

curl -X PATCH https://sso.example.com/api/organizations/acme-corp/webhooks/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{
    "is_active": false,
    "events": [
      "user.login.success",
      "user.signup.success",
      "user.mfa.enabled",
      "user.mfa.disabled"
    ]
  }'

Response (200 OK):

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "Production Webhook",
  "url": "https://api.acme.com/webhooks/sso",
  "events": [
    "user.login.success",
    "user.signup.success",
    "user.mfa.enabled",
    "user.mfa.disabled"
  ],
  "is_active": false,
  "created_at": "2025-01-15T10:30:00Z",
  "updated_at": "2025-01-15T12:45:00Z"
}

Error Responses:

  • 400 Bad Request: Invalid input (empty name/url, invalid URL format, invalid event types, duplicate name)
    {
      "error": "A webhook with this name already exists"
    }
    
  • 401 Unauthorized: Missing or invalid JWT token
  • 403 Forbidden: User is not an admin or owner
  • 404 Not Found: Organization or webhook not found

Notes:

  • Only provided fields are updated
  • Webhook name must be unique if changed
  • At least one event must be specified if updating events
  • Setting is_active: false disables the webhook without deleting delivery history

DELETE /api/organizations/:org_slug/webhooks/:webhook_id

Delete a webhook. This action is permanent and will also delete all delivery history.

Authentication: Required (Organization Management JWT)

Permissions: Organization owner or admin

Path Parameters:

Parameter Type Description
org_slug string Organization slug
webhook_id string Webhook ID

Query Parameters: None

Request Headers:

Authorization: Bearer {jwt_token}

Example Request:

curl -X DELETE https://sso.example.com/api/organizations/acme-corp/webhooks/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."

Response: 200 OK with empty JSON object {}

Error Responses:

  • 401 Unauthorized: Missing or invalid JWT token
  • 403 Forbidden: User is not an admin or owner
  • 404 Not Found: Organization or webhook not found

Warning: This action is irreversible. All delivery history associated with this webhook will be permanently deleted.


Webhook Delivery

GET /api/organizations/:org_slug/webhooks/:webhook_id/deliveries

Get paginated delivery history for a webhook, including request payload, response status, and retry information.

Authentication: Required (Organization Management JWT)

Permissions: Organization owner or admin

Path Parameters:

Parameter Type Description
org_slug string Organization slug
webhook_id string Webhook ID

Query Parameters:

Parameter Type Required Default Description
page integer No 1 Page number (minimum: 1)
limit integer No 50 Results per page (maximum: 100)
event_type string No - Filter by event type
delivered boolean No - Filter by delivery status (true/false)

Request Headers:

Authorization: Bearer {jwt_token}

Example Request:

curl -X GET "https://sso.example.com/api/organizations/acme-corp/webhooks/550e8400-e29b-41d4-a716-446655440000/deliveries?page=1&limit=20&event_type=user.login.success" \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."

Response (200 OK):

{
  "deliveries": [
    {
      "id": "770e8400-e29b-41d4-a716-446655440222",
      "webhook_id": "550e8400-e29b-41d4-a716-446655440000",
      "webhook_name": "Production Webhook",
      "event_type": "user.login.success",
      "payload": {
        "event": "user.login.success",
        "timestamp": "2025-01-15T10:35:00Z",
        "organization_id": "org-123",
        "actor_user_id": "user-456",
        "actor_email": "user@example.com",
        "data": {
          "provider": "github",
          "service_id": "service-789"
        }
      },
      "response_status_code": 200,
      "response_body": "{\"status\":\"received\"}",
      "attempt_count": 1,
      "max_attempts": 5,
      "next_retry_at": null,
      "delivered": true,
      "delivery_error": null,
      "created_at": "2025-01-15T10:35:00Z",
      "updated_at": "2025-01-15T10:35:01Z"
    },
    {
      "id": "880e8400-e29b-41d4-a716-446655440333",
      "webhook_id": "550e8400-e29b-41d4-a716-446655440000",
      "webhook_name": "Production Webhook",
      "event_type": "user.signup.success",
      "payload": {
        "event": "user.signup.success",
        "timestamp": "2025-01-15T10:30:00Z",
        "organization_id": "org-123",
        "actor_user_id": "user-789",
        "actor_email": "newuser@example.com",
        "data": {
          "provider": "google",
          "service_id": "service-789"
        }
      },
      "response_status_code": 500,
      "response_body": "{\"error\":\"Internal Server Error\"}",
      "attempt_count": 3,
      "max_attempts": 5,
      "next_retry_at": "2025-01-15T10:45:00Z",
      "delivered": false,
      "delivery_error": "HTTP 500: Internal Server Error",
      "created_at": "2025-01-15T10:30:00Z",
      "updated_at": "2025-01-15T10:40:00Z"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 156,
    "total_pages": 8,
    "has_next": true,
    "has_prev": false
  }
}

Response Fields:

Delivery Object:

  • id (string): Delivery unique identifier
  • webhook_id (string): Webhook ID
  • webhook_name (string): Webhook name
  • event_type (string): Event type that triggered delivery
  • payload (object): Full webhook payload sent
  • response_status_code (integer, nullable): HTTP status code from target endpoint
  • response_body (string, nullable): Response body from target endpoint
  • attempt_count (integer): Number of delivery attempts
  • max_attempts (integer): Maximum retry attempts (5)
  • next_retry_at (string, nullable): ISO 8601 timestamp of next retry
  • delivered (boolean): Whether delivery was successful
  • delivery_error (string, nullable): Error message if delivery failed
  • created_at (string): ISO 8601 timestamp of first delivery attempt
  • updated_at (string): ISO 8601 timestamp of last delivery attempt

Pagination Object:

  • page (integer): Current page number
  • limit (integer): Results per page
  • total (integer): Total number of deliveries
  • total_pages (integer): Total number of pages
  • has_next (boolean): Whether there is a next page
  • has_prev (boolean): Whether there is a previous page

Error Responses:

  • 401 Unauthorized: Missing or invalid JWT token
  • 403 Forbidden: User is not an admin or owner
  • 404 Not Found: Organization or webhook not found

Event Types

GET /api/organizations/:org_slug/webhooks/event-types

Get a list of all available webhook event types with descriptions and categories.

Authentication: Required (Organization Management JWT)

Permissions: Organization owner or admin

Path Parameters:

Parameter Type Description
org_slug string Organization slug

Query Parameters: None

Request Headers:

Authorization: Bearer {jwt_token}

Example Request:

curl -X GET https://sso.example.com/api/organizations/acme-corp/webhooks/event-types \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."

Response (200 OK):

[
  {
    "value": "user.signup.success",
    "label": "USER SIGNUP SUCCESS",
    "category": "User Management"
  },
  {
    "value": "user.login.success",
    "label": "USER LOGIN SUCCESS",
    "category": "User Management"
  },
  {
    "value": "user.login.failed",
    "label": "USER LOGIN FAILED",
    "category": "User Management"
  },
  {
    "value": "user.logout",
    "label": "USER LOGOUT",
    "category": "User Management"
  },
  {
    "value": "user.mfa.enabled",
    "label": "USER MFA ENABLED",
    "category": "User Management"
  }
]

Response Fields:

  • value (string): Event type identifier (use this in webhook subscription)
  • label (string): Human-readable event name
  • category (string): Event category

Event Categories:

  • User Management
  • Service Management
  • Organization Management
  • Plan Management
  • Subscription Management
  • Invitation Management
  • Security
  • API Keys
  • Custom Domains
  • Branding

Error Responses:

  • 401 Unauthorized: Missing or invalid JWT token
  • 403 Forbidden: User is not an admin or owner
  • 404 Not Found: Organization not found

Available Event Types

Webhooks can subscribe to the following event types:

User Management

Lifecycle Events:

  • user.signup.success - New user account created
  • user.login.success - Successful user authentication
  • user.login.failed - Failed authentication attempt
  • user.logout - User logged out

MFA Events:

  • user.mfa.enabled - User enabled MFA
  • user.mfa.disabled - User disabled MFA
  • user.mfa.verify.success - Successful MFA verification
  • user.mfa.verify.failed - Failed MFA verification

Administrative Actions:

  • user.invited - User invited to organization
  • user.joined - User accepted invitation
  • user.removed - User removed from organization
  • user.role_updated - User role changed

Service Management

  • service.created - New service created
  • service.updated - Service details updated
  • service.deleted - Service deleted
  • service.oauth_credentials.updated - OAuth credentials updated

Organization Management

  • organization.updated - Organization details updated
  • organization.smtp.configured - SMTP settings configured
  • organization.smtp.removed - SMTP settings removed

Plan Management

  • plan.created - New subscription plan created
  • plan.updated - Plan updated
  • plan.deleted - Plan deleted

Subscription Management

  • subscription.created - User subscribed to plan
  • subscription.updated - Subscription modified
  • subscription.canceled - Subscription canceled

Invitation Management

  • invitation.accepted - Invitation accepted
  • invitation.declined - Invitation declined
  • invitation.expired - Invitation expired
  • invitation.revoked - Invitation revoked

Security Events

  • security.mfa.enabled - Organization-level MFA enabled
  • security.mfa.disabled - Organization-level MFA disabled
  • security.password.changed - Password changed

API Key Management

  • api_key.created - API key created
  • api_key.deleted - API key deleted

Custom Domains & Branding

  • domain.set - Custom domain set
  • domain.verified - Custom domain verified
  • domain.deleted - Custom domain deleted
  • branding.updated - Branding settings updated

Webhook Payload Structure

All webhook deliveries follow a consistent payload structure:

{
  "event": "user.login.success",
  "timestamp": "2025-01-15T10:30:00Z",
  "organization_id": "org-uuid",
  "actor_user_id": "user-uuid",
  "actor_email": "user@example.com",
  "target_type": "user",
  "target_id": "user-uuid",
  "data": {
    "service_id": "service-uuid",
    "provider": "github",
    "custom_field": "value"
  }
}

Common Fields:

  • event (string): Event type that triggered the webhook
  • timestamp (string): ISO 8601 timestamp of event occurrence
  • organization_id (string): Organization ID (if applicable)
  • actor_user_id (string): User ID who triggered the event (if applicable)
  • actor_email (string): Email of user who triggered the event (if applicable)
  • target_type (string): Type of resource affected (e.g., “user”, “service”, “organization”)
  • target_id (string): ID of the affected resource
  • data (object): Event-specific additional data

Example Payloads

User Signup:

{
  "event": "user.signup.success",
  "timestamp": "2025-01-15T10:30:00Z",
  "organization_id": "org-123",
  "actor_user_id": "user-456",
  "actor_email": "newuser@example.com",
  "target_type": "user",
  "target_id": "user-456",
  "data": {
    "provider": "google",
    "service_id": "service-789"
  }
}

User Login:

{
  "event": "user.login.success",
  "timestamp": "2025-01-15T10:30:00Z",
  "organization_id": "org-123",
  "actor_user_id": "user-456",
  "actor_email": "user@example.com",
  "target_type": "user",
  "target_id": "user-456",
  "data": {
    "provider": "github",
    "service_id": "service-789"
  }
}

MFA Enabled:

{
  "event": "user.mfa.enabled",
  "timestamp": "2025-01-15T10:30:00Z",
  "organization_id": "org-123",
  "actor_user_id": "user-456",
  "actor_email": "user@example.com",
  "target_type": "user",
  "target_id": "user-456",
  "data": {
    "method": "totp",
    "backup_codes_count": 10
  }
}

Service Created:

{
  "event": "service.created",
  "timestamp": "2025-01-15T10:30:00Z",
  "organization_id": "org-123",
  "actor_user_id": "admin-789",
  "actor_email": "admin@acme.com",
  "target_type": "service",
  "target_id": "service-456",
  "data": {
    "service_name": "Mobile App",
    "service_slug": "mobile-app",
    "service_type": "mobile"
  }
}

Security

Signature Verification

All webhook deliveries include security headers for authenticity verification:

Headers:

  • X-Webhook-Signature: HMAC-SHA256 signature of the payload
  • X-Webhook-Timestamp: Unix timestamp in seconds since epoch (UTC)

Signature Format:

sha256={hmac_hex_digest}

Verification Example (Node.js):

const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  // Create HMAC using webhook secret
  const hmac = crypto.createHmac('sha256', secret);

  // Update with stringified payload
  hmac.update(JSON.stringify(payload));

  // Generate expected signature
  const expectedSignature = `sha256=${hmac.digest('hex')}`;

  // Use timing-safe comparison
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

// Express.js middleware example
app.post('/webhooks/sso', (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const timestamp = req.headers['x-webhook-timestamp'];
  const payload = req.body;

  // Verify timestamp to prevent replay attacks
  const currentTime = Math.floor(Date.now() / 1000);
  if (Math.abs(currentTime - timestamp) > 300) {
    return res.status(400).json({ error: 'Timestamp too old' });
  }

  // Verify signature
  if (!verifyWebhookSignature(payload, signature, WEBHOOK_SECRET)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Process webhook payload
  console.log('Received event:', payload.event);
  res.status(200).json({ status: 'received' });
});

Verification Example (Python):

import hmac
import hashlib
import time
import json

def verify_webhook_signature(payload, signature, secret):
    # Create HMAC using webhook secret
    hmac_gen = hmac.new(
        secret.encode('utf-8'),
        json.dumps(payload).encode('utf-8'),
        hashlib.sha256
    )

    # Generate expected signature
    expected_signature = f"sha256={hmac_gen.hexdigest()}"

    # Use constant-time comparison
    return hmac.compare_digest(signature, expected_signature)

# Flask example
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/webhooks/sso', methods=['POST'])
def handle_webhook():
    signature = request.headers.get('X-Webhook-Signature')
    timestamp = int(request.headers.get('X-Webhook-Timestamp'))
    payload = request.json

    # Verify timestamp
    current_time = int(time.time())
    if abs(current_time - timestamp) > 300:
        return jsonify({'error': 'Timestamp too old'}), 400

    # Verify signature
    if not verify_webhook_signature(payload, signature, WEBHOOK_SECRET):
        return jsonify({'error': 'Invalid signature'}), 401

    # Process webhook
    print(f"Received event: {payload['event']}")
    return jsonify({'status': 'received'}), 200

Security Best Practices:

  • Always verify signatures before processing webhook payloads
  • Validate timestamp to prevent replay attacks (within 5 minutes)
  • Use constant-time comparison to prevent timing attacks
  • Store webhook secrets securely (environment variables, secret managers)
  • Use HTTPS endpoints only
  • Implement rate limiting on your webhook endpoints

Retry Logic

Failed webhook deliveries are automatically retried with exponential backoff:

Retry Configuration:

  • Max Retries: 5 attempts total
  • Initial Delay: 5 seconds
  • Max Delay: 30 minutes
  • Backoff Formula: delay = base_delay � 2^(attempt - 1) + jitter
  • Jitter: 0-9 seconds (prevents thundering herd)

Retry Schedule Example:

  1. Attempt 1: Immediate
  2. Attempt 2: 5 seconds + jitter
  3. Attempt 3: 10 seconds + jitter
  4. Attempt 4: 20 seconds + jitter
  5. Attempt 5: 40 seconds + jitter (capped at 30 minutes)

Success Criteria:

  • HTTP status codes 2xx (200-299) are considered successful
  • All other status codes (3xx, 4xx, 5xx) trigger retries
  • Network timeouts and connection errors trigger retries

Retry Headers: Each retry includes headers indicating the attempt number:

X-Webhook-Delivery-Id: {delivery_id}
X-Webhook-Attempt: {attempt_number}

Best Practices for Webhook Endpoints:

  • Return 2xx status codes immediately upon receipt
  • Process webhooks asynchronously in background jobs
  • Implement idempotency using X-Webhook-Delivery-Id
  • Return non-2xx only for malformed payloads or authentication failures
  • Avoid long-running operations in webhook handlers

Rate Limiting

Webhook endpoints should implement rate limiting to prevent abuse:

Recommended Limits:

  • Per Webhook: 100 deliveries per minute
  • Per Organization: 1000 deliveries per minute across all webhooks

Rate Limit Headers:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1642244400

Error Responses

Common Errors

400 Bad Request:

{
  "error": "Webhook name cannot be empty",
  "error_code": "BAD_REQUEST",
  "timestamp": "2025-01-15T10:30:00Z"
}

401 Unauthorized:

{
  "error": "Missing or invalid Authorization header",
  "error_code": "UNAUTHORIZED",
  "timestamp": "2025-01-15T10:30:00Z"
}

403 Forbidden:

{
  "error": "Organization admin or owner role required",
  "error_code": "FORBIDDEN",
  "timestamp": "2025-01-15T10:30:00Z"
}

404 Not Found:

{
  "error": "Webhook not found",
  "error_code": "NOT_FOUND",
  "timestamp": "2025-01-15T10:30:00Z"
}

Complete Workflow Example

1. Create Webhook

curl -X POST https://sso.example.com/api/organizations/acme-corp/webhooks \
  -H "Authorization: Bearer {jwt}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Production Events",
    "url": "https://api.acme.com/webhooks/sso",
    "events": ["user.login.success", "user.signup.success"]
  }'

2. Implement Webhook Endpoint

// Express.js webhook handler
app.post('/webhooks/sso', express.json(), (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const payload = req.body;

  // Verify signature
  if (!verifySignature(payload, signature, process.env.WEBHOOK_SECRET)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Process event
  switch (payload.event) {
    case 'user.login.success':
      console.log(`User ${payload.actor_email} logged in`);
      break;
    case 'user.signup.success':
      console.log(`New user ${payload.actor_email} signed up`);
      // Send welcome email, create account in CRM, etc.
      break;
  }

  res.status(200).json({ status: 'received' });
});

3. Monitor Deliveries

curl -X GET "https://sso.example.com/api/organizations/acme-corp/webhooks/{webhook_id}/deliveries?page=1&limit=50" \
  -H "Authorization: Bearer {jwt}"

4. Handle Failed Deliveries

# Check for failed deliveries
curl -X GET "https://sso.example.com/api/organizations/acme-corp/webhooks/{webhook_id}/deliveries?delivered=false" \
  -H "Authorization: Bearer {jwt}"

# Review error messages and fix endpoint
# Optionally disable webhook while fixing
curl -X PATCH https://sso.example.com/api/organizations/acme-corp/webhooks/{webhook_id} \
  -H "Authorization: Bearer {jwt}" \
  -H "Content-Type: application/json" \
  -d '{ "is_active": false }'

Best Practices

Webhook Design

  • Subscribe only to events you need to reduce traffic
  • Use descriptive webhook names for easy identification
  • Implement proper error handling and logging
  • Return 2xx status codes quickly (process asynchronously)
  • Use signature verification for security

Endpoint Implementation

  • Implement idempotency using delivery IDs
  • Process webhooks in background jobs/queues
  • Log all webhook deliveries for debugging
  • Return non-2xx only for invalid requests
  • Implement rate limiting and request validation

Monitoring & Maintenance

  • Monitor delivery success rates
  • Set up alerts for failed deliveries
  • Review delivery history regularly
  • Test webhook endpoints before enabling
  • Document your webhook integrations

Security

  • Always verify webhook signatures
  • Validate timestamps to prevent replay attacks
  • Use HTTPS endpoints only
  • Store webhook secrets securely
  • Implement IP allowlisting if possible
  • Rate limit webhook endpoints