Getting Started / How It Works

    How It Works

    A technical walkthrough of how Paggio captures, stores, forwards, and replays webhook events.

    stripe.com──POST──▶paggio.dev/api/webhook/:slug
    1.Returns 200 OK immediately (non-blocking)
    2.Stores full event → webhook_events table
    3.Detects source from headers
    4.Forwards to destination_url (if set)
    5.Updates status → delivered / failed
    6.Sends Discord/Slack notification (if configured)

    1. Endpoint creation

    When you create an endpoint, Paggio generates a unique slug and stores the endpoint configuration in the database (name, destination URL, secret key). The webhook URL is:

    text
    https://paggio.dev/api/webhook/:slug

    2. Capturing events

    When a POST request arrives at the webhook URL, Paggio:

    • 1. Returns 200 {"received": true} immediately — so the sender doesn't time out
    • 2. Reads the full request body and all headers
    • 3. Detects the source (Stripe, GitHub, Shopify, etc.) from the request headers
    • 4. Saves the event to the webhook_events table with status pending

    3. Forwarding

    If the endpoint has a destination URL configured, Paggio forwards the original request (headers + body) to that URL. Hop-by-hop headers (connection, transfer-encoding, etc.) are stripped. A 10-second timeout is enforced.

    • 2xx response → event status set to delivered
    • 4xx / 5xx response → event status set to failed
    • Network error / timeout → event status set to failed
    • No destination URL → event status set to captured
    Destination URL is optional:You can leave the destination URL blank if you just want to capture and inspect events without forwarding them. Events will be stored with status captured.

    4. Event data stored

    Every captured event stores the following:

    json
    {
      "id": "uuid",
      "endpoint_id": "uuid",
      "user_id": "uuid",
      "source": "stripe",
      "event_type": "payment_intent.succeeded",
      "method": "POST",
      "headers": { "stripe-signature": "t=...,v1=..." },
      "payload": { "id": "evt_123", "type": "payment_intent.succeeded" },
      "remote_addr": "54.187.205.235",
      "status": "delivered",
      "response_status": 200,
      "response_body": "{ \"ok\": true }",
      "latency_ms": 142,
      "received_at": "2024-03-10T15:23:41.000Z"
    }

    5. Replays

    A replay takes a stored event and re-sends the original payload + headers to a target URL. Each replay is saved as a separate record in the replay_attempts table — replays never modify the original event.

    Replay targets can be: original destination, localhost:3000 (for local dev), or any custom URL.

    6. Source detection

    Paggio inspects request headers to identify the source. For example:

    typescript
    // Examples of what Paggio checks
    if (headers.get('stripe-signature'))         return 'stripe';
    if (headers.get('x-github-event'))           return 'github';
    if (headers.get('x-shopify-hmac-sha256'))    return 'shopify';
    if (headers.get('linear-signature'))         return 'linear';
    if (headers.get('x-vercel-signature'))       return 'vercel';
    // ...15+ sources total