Files
alpinebits_python/WEBHOOK_REFACTORING_SUMMARY.md
Jonas Linter b77a0be80f feat: Add hotel and webhook endpoint management
- Introduced Hotel and WebhookEndpoint models to manage hotel configurations and webhook settings.
- Implemented sync_config_to_database function to synchronize hotel data from configuration to the database.
- Added HotelService for accessing hotel configurations and managing customer data.
- Created WebhookProcessor interface and specific processors for handling different webhook types (Wix form and generic).
- Enhanced webhook processing logic to handle incoming requests and create/update reservations and customers.
- Added logging for better traceability of operations related to hotels and webhooks.
2025-11-25 12:05:48 +01:00

13 KiB

Webhook System Refactoring - Implementation Summary

Overview

This document summarizes the webhook system refactoring that was implemented to solve race conditions, unify webhook handling, add security through randomized URLs, and migrate hotel configuration to the database.

What Was Implemented

1. Database Models

File: src/alpine_bits_python/db.py

Added three new database models:

Hotel Model

  • Stores hotel configuration (previously in alpine_bits_auth config.yaml section)
  • Fields: hotel_id, hotel_name, username, password_hash (bcrypt), meta/google account IDs, push endpoint config
  • Relationships: one-to-many with webhook_endpoints

WebhookEndpoint Model

  • Stores webhook configurations per hotel
  • Each hotel can have multiple webhook types (wix_form, generic, etc.)
  • Each endpoint has a unique randomized webhook_secret (64-char URL-safe string)
  • Fields: webhook_secret, webhook_type, hotel_id, description, is_enabled

WebhookRequest Model

  • Tracks incoming webhooks for deduplication and retry handling
  • Uses SHA256 payload hashing to detect duplicates
  • Status tracking: pending → processing → completed/failed
  • Supports payload purging after retention period
  • Fields: payload_hash, status, payload_json, retry_count, created_at, processing timestamps

2. Alembic Migration

File: alembic/versions/2025_11_25_1155-e7ee03d8f430_add_hotels_and_webhook_tables.py

  • Creates all three tables with appropriate indexes
  • Includes composite indexes for query performance
  • Fully reversible (downgrade supported)

3. Hotel Service

File: src/alpine_bits_python/hotel_service.py

Key Functions:

  • hash_password() - Bcrypt password hashing (12 rounds)
  • verify_password() - Bcrypt password verification
  • generate_webhook_secret() - Cryptographically secure secret generation
  • sync_config_to_database() - Syncs config.yaml to database at startup
    • Creates/updates hotels from alpine_bits_auth config
    • Auto-generates default webhook endpoints if missing
    • Idempotent - safe to run on every startup

HotelService Class:

  • get_hotel_by_id() - Look up hotel by hotel_id
  • get_hotel_by_webhook_secret() - Look up hotel and endpoint by webhook secret
  • get_hotel_by_username() - Look up hotel by AlpineBits username

4. Webhook Processor Interface

File: src/alpine_bits_python/webhook_processor.py

Architecture:

  • Protocol-based interface for webhook processors
  • Registry pattern for managing processor types
  • Two built-in processors:
    • WixFormProcessor - Wraps existing process_wix_form_submission()
    • GenericWebhookProcessor - Wraps existing process_generic_webhook_submission()

Benefits:

  • Easy to add new webhook types
  • Clean separation of concerns
  • Type-safe processor interface

5. Config-to-Database Sync

File: src/alpine_bits_python/db_setup.py

  • Added call to sync_config_to_database() in run_startup_tasks()
  • Runs on every application startup (primary worker only)
  • Logs statistics about created/updated hotels and endpoints

6. Unified Webhook Handler

File: src/alpine_bits_python/api.py

Endpoint: POST /api/webhook/{webhook_secret}

Flow:

  1. Look up webhook_endpoint by webhook_secret
  2. Parse and hash payload (SHA256)
  3. Check for duplicate using SELECT FOR UPDATE SKIP LOCKED
  4. Return immediately if already processed (idempotent)
  5. Create WebhookRequest with status='processing'
  6. Route to appropriate processor based on webhook_type
  7. Update status to 'completed' or 'failed'
  8. Return response with webhook_id

Race Condition Prevention:

  • PostgreSQL row-level locking with SKIP LOCKED
  • Atomic status transitions
  • Payload hash uniqueness constraint
  • If duplicate detected during processing, return success (not error)

Features:

  • Gzip decompression support
  • Payload size limit (10MB)
  • Automatic retry for failed webhooks
  • Detailed error logging
  • Source IP and user agent tracking

7. Cleanup and Monitoring

File: src/alpine_bits_python/api.py

Functions:

  • cleanup_stale_webhooks() - Reset webhooks stuck in 'processing' (worker crash recovery)
  • purge_old_webhook_payloads() - Remove payload_json from old completed webhooks (keeps metadata)
  • periodic_webhook_cleanup() - Runs both cleanup tasks

Scheduling:

  • Periodic task runs every 5 minutes (primary worker only)
  • Stale timeout: 10 minutes
  • Payload retention: 7 days before purge

8. Processor Initialization

File: src/alpine_bits_python/api.py - lifespan function

  • Calls initialize_webhook_processors() during application startup
  • Registers all built-in processors (wix_form, generic)

What Was NOT Implemented (Future Work)

1. Legacy Endpoint Updates

The existing /api/webhook/wix-form and /api/webhook/generic endpoints still work as before. They could be updated to:

  • Look up hotel from database
  • Find appropriate webhook endpoint
  • Redirect to unified handler

This is backward compatible, so it's not urgent.

2. AlpineBits Authentication Updates

The validate_basic_auth() function still reads from config.yaml. It could be updated to:

  • Query hotels table by username
  • Use bcrypt to verify password
  • Return Hotel object instead of just credentials

This requires changing the AlpineBits auth flow, so it's a separate task.

3. Admin Endpoints

Could add endpoints for:

  • GET /admin/webhooks/stats - Processing statistics
  • GET /admin/webhooks/failed - Recent failures
  • POST /admin/webhooks/{id}/retry - Manually retry failed webhook
  • GET /admin/hotels - List all hotels with webhook URLs
  • POST /admin/hotels/{id}/webhook - Create new webhook endpoint

4. Tests

Need to write tests for:

  • Hotel service functions
  • Webhook processors
  • Unified webhook handler
  • Race condition scenarios (concurrent identical webhooks)
  • Deduplication logic
  • Cleanup functions

How to Use

1. Run Migration

uv run alembic upgrade head

2. Start Application

The application will automatically:

  • Sync config.yaml hotels to database
  • Generate default webhook endpoints for each hotel
  • Log webhook URLs to console
  • Start periodic cleanup tasks

3. Use New Webhook URLs

Each hotel will have webhook URLs like:

POST /api/webhook/{webhook_secret}

The webhook_secret is logged at startup, or you can query the database:

SELECT h.hotel_id, h.hotel_name, we.webhook_type, we.webhook_secret
FROM hotels h
JOIN webhook_endpoints we ON h.hotel_id = we.hotel_id
WHERE we.is_enabled = true;

Example webhook URL:

https://your-domain.com/api/webhook/x7K9mPq2rYv8sN4jZwL6tH1fBd3gCa5eFhIk0uMoQp-RnVxWy

4. Legacy Endpoints Still Work

Existing integrations using /api/webhook/wix-form or /api/webhook/generic will continue to work without changes.

Benefits Achieved

1. Race Condition Prevention

  • PostgreSQL row-level locking prevents duplicate processing
  • Atomic status transitions ensure only one worker processes each webhook
  • Stale webhook cleanup recovers from worker crashes

2. Unified Webhook Handling

  • Single entry point with pluggable processor interface
  • Easy to add new webhook types
  • Consistent error handling and logging

3. Secure Webhook URLs

  • Randomized 64-character URL-safe secrets
  • One unique secret per hotel/webhook-type combination
  • No authentication needed (secret provides security)

4. Database-Backed Configuration

  • Hotel config automatically synced from config.yaml
  • Passwords hashed with bcrypt
  • Webhook endpoints stored in database
  • Easy to manage via SQL queries

5. Payload Management

  • Automatic purging of old payloads (keeps metadata)
  • Configurable retention period
  • Efficient storage usage

6. Observability

  • Webhook requests tracked in database
  • Status history maintained
  • Source IP and user agent logged
  • Retry count tracked
  • Error messages stored

Configuration

Existing Config (config.yaml)

No changes required! The existing alpine_bits_auth section is still read and synced to the database automatically:

alpine_bits_auth:
  - hotel_id: "123"
    hotel_name: "Example Hotel"
    username: "hotel123"
    password: "secret"  # Will be hashed with bcrypt in database
    meta_account: "1234567890"
    google_account: "9876543210"
    push_endpoint:
      url: "https://example.com/push"
      token: "token123"
      username: "pushuser"

New Optional Config

You can add webhook-specific configuration:

webhooks:
  stale_timeout_minutes: 10        # Timeout for stuck webhooks (default: 10)
  payload_retention_days: 7        # Days before purging payload_json (default: 7)
  cleanup_interval_minutes: 5      # How often to run cleanup (default: 5)

Database Queries

View All Webhook URLs

SELECT
    h.hotel_id,
    h.hotel_name,
    we.webhook_type,
    we.webhook_secret,
    'https://your-domain.com/api/webhook/' || we.webhook_secret AS webhook_url
FROM hotels h
JOIN webhook_endpoints we ON h.hotel_id = we.hotel_id
WHERE we.is_enabled = true
ORDER BY h.hotel_id, we.webhook_type;

View Recent Webhook Activity

SELECT
    wr.id,
    wr.created_at,
    h.hotel_name,
    we.webhook_type,
    wr.status,
    wr.retry_count,
    wr.created_customer_id,
    wr.created_reservation_id
FROM webhook_requests wr
JOIN webhook_endpoints we ON wr.webhook_endpoint_id = we.id
JOIN hotels h ON we.hotel_id = h.hotel_id
ORDER BY wr.created_at DESC
LIMIT 50;

View Failed Webhooks

SELECT
    wr.id,
    wr.created_at,
    h.hotel_name,
    we.webhook_type,
    wr.retry_count,
    wr.last_error
FROM webhook_requests wr
JOIN webhook_endpoints we ON wr.webhook_endpoint_id = we.id
JOIN hotels h ON we.hotel_id = h.hotel_id
WHERE wr.status = 'failed'
ORDER BY wr.created_at DESC;

Webhook Statistics

SELECT
    h.hotel_name,
    we.webhook_type,
    COUNT(*) AS total_requests,
    SUM(CASE WHEN wr.status = 'completed' THEN 1 ELSE 0 END) AS completed,
    SUM(CASE WHEN wr.status = 'failed' THEN 1 ELSE 0 END) AS failed,
    SUM(CASE WHEN wr.status = 'processing' THEN 1 ELSE 0 END) AS processing,
    AVG(EXTRACT(EPOCH FROM (wr.processing_completed_at - wr.processing_started_at))) AS avg_processing_seconds
FROM webhook_requests wr
JOIN webhook_endpoints we ON wr.webhook_endpoint_id = we.id
JOIN hotels h ON we.hotel_id = h.hotel_id
WHERE wr.created_at > NOW() - INTERVAL '7 days'
GROUP BY h.hotel_name, we.webhook_type
ORDER BY total_requests DESC;

Security Considerations

1. Password Storage

  • Passwords are hashed with bcrypt (12 rounds)
  • Plain text passwords never stored in database
  • Config sync does NOT update password_hash (security)
    • To change password: manually update database or delete hotel record

2. Webhook Secrets

  • Generated using secrets.token_urlsafe(48) (cryptographically secure)
  • 64-character URL-safe strings
  • Unique per endpoint
  • Act as API keys (no additional auth needed)

3. Payload Size Limits

  • 10MB maximum payload size
  • Prevents memory exhaustion attacks
  • Configurable in code

4. Rate Limiting

  • Existing rate limiting still applies
  • Uses slowapi with configured limits

Next Steps

  1. Test Migration - Run uv run alembic upgrade head in test environment
  2. Verify Sync - Start application and check logs for hotel sync statistics
  3. Test Webhook URLs - Send test payloads to new unified endpoint
  4. Monitor Performance - Watch for any issues with concurrent webhooks
  5. Add Tests - Write comprehensive test suite
  6. Update Documentation - Document webhook URLs for external integrations
  7. Consider Admin UI - Build admin interface for managing hotels/webhooks

Files Modified

  1. src/alpine_bits_python/db.py - Added Hotel, WebhookEndpoint, WebhookRequest models
  2. src/alpine_bits_python/db_setup.py - Added config sync call
  3. src/alpine_bits_python/api.py - Added unified handler, cleanup functions, processor initialization
  4. src/alpine_bits_python/hotel_service.py - NEW FILE
  5. src/alpine_bits_python/webhook_processor.py - NEW FILE
  6. alembic/versions/2025_11_25_1155-*.py - NEW MIGRATION

Rollback Plan

If issues are discovered:

  1. Rollback Migration:

    uv run alembic downgrade -1
    
  2. Revert Code:

    git revert <commit-hash>
    
  3. Fallback:

    • Legacy endpoints (/webhook/wix-form, /webhook/generic) still work
    • No breaking changes to existing integrations
    • Can disable new unified handler by removing route

Success Metrics

  • No duplicate customers/reservations created from concurrent webhooks
  • Webhook processing latency maintained
  • Zero data loss during migration
  • Backward compatibility maintained
  • Memory usage stable (payload purging working)
  • Error rate < 1% for webhook processing

Support

For issues or questions:

  1. Check application logs for errors
  2. Query webhook_requests table for failed webhooks
  3. Review this document for configuration options
  4. Check GitHub issues for known problems