Webhooks
Stripe

Stripe Webhook - Event reception

Endpoint to receive Stripe webhooks and process payment events.

⚠️ IMPORTANT: Security

This endpoint DOES NOT USE standard Bearer API key authentication. Instead, it uses Stripe signature verification to ensure that requests genuinely come from Stripe.

Signature verification

Each webhook is verified with HMAC SHA256 according to Stripe documentation:

  1. Extract the timestamp and v1 signature from the Stripe-Signature header
  2. Build the signed payload: timestamp + "." + payload_body
  3. Compute HMAC SHA256 with the webhook secret (STRIPE_WEBHOOK_SECRET)
  4. Constant-time comparison with the received signature
  5. Validate the timestamp (reject webhooks > 5 minutes old)

Stripe-Signature header format:

t=1234567890,v1=abc123def456...

Replay attack protection

Webhooks with a timestamp too old (> 5 minutes) are rejected to prevent replay attacks.

Handled events

checkout.session.completed

Triggered: When a user completes a payment via Stripe Checkout Session.

Actions:

  1. Extract metadata (tenant_id, user_id, package_id)
  2. Idempotency check (avoid double processing)
  3. Retrieve the package from DB via stripe_price_id
  4. Credit the user's account
  5. Create a purchase type transaction

Idempotency:

  • Check by payment_intent_id in existing transactions
  • Check by session_id in transaction reasons
  • If already processed, returns 200 OK without action

Payload example:

{
  "id": "evt_test_123",
  "type": "checkout.session.completed",
  "data": {
    "object": {
      "id": "cs_test_123",
      "payment_intent": "pi_test_123",
      "amount_total": 2990,
      "metadata": {
        "tenant_id": "tenant_123",
        "user_id": "user_456",
        "package_id": "550e8400-e29b-41d4-a716-446655440000"
      },
      "line_items": {
        "data": [{
          "price": {
            "id": "price_test_123"
          }
        }]
      }
    }
  }
}

payment_intent.succeeded

Triggered: When a Stripe PaymentIntent is confirmed successfully.

Actions:

  • Logging for individual payment traceability
  • No action on credits (handled by checkout.session.completed)

Usage: Primarily for individual payments (non-credits).

payment_intent.payment_failed

Triggered: When a payment fails.

Actions:

  • Logging for traceability
  • No action on credits (no deduction)

Stripe Dashboard Configuration

For this endpoint to work, you must configure a webhook in Stripe Dashboard:

  1. Go to Developers → Webhooks
  2. Click Add endpoint
  3. URL: https://api.faireplace.com/api/webhooks/stripe (or dev URL for testing)
  4. Select the events:
    • ✅ checkout.session.completed
    • ✅ payment_intent.succeeded
    • ✅ payment_intent.payment_failed
  5. Copy the Signing secret (whsec_...) and configure it in STRIPE_WEBHOOK_SECRET

Responses

The endpoint always returns 200 OK for handled or ignored events, per Stripe recommendations. This prevents Stripe from retrying webhooks indefinitely.

Exceptions:

  • 401 Unauthorized: Invalid or missing signature
  • 400 Bad Request: Invalid payload or missing metadata
  • 500 Internal Server Error: Processing error

Request example

curl -X POST https://api.faireplace.com/v1/webhooks/stripe \
  -H "Stripe-Signature: t=1234567890,v1=abc123def456..." \
  -H "Content-Type: application/json" \
  -d '{
    "id": "evt_test_123",
    "type": "checkout.session.completed",
    "data": {
      "object": {
        "id": "cs_test_123",
        "payment_intent": "pi_test_123",
        "amount_total": 2990,
        "metadata": {
          "tenant_id": "tenant_123",
          "user_id": "user_456",
          "package_id": "550e8400-e29b-41d4-a716-446655440000"
        }
      }
    }
  }'

Response:

{
  "received": true
}

Logs and traceability

All webhooks are logged with:

  • Event type
  • Stripe Event ID
  • Session ID or Payment Intent ID
  • Tenant ID and User ID (from metadata)
  • Processing result (success, idempotency, error)

Error handling

In case of error during webhook processing:

  1. The error is logged with all details
  2. A 500 response is returned to Stripe
  3. Stripe will automatically retry the webhook (up to 3 attempts)

Important: Validation errors (missing metadata, etc.) return 400 and will not be retried by Stripe.

post/webhooks/stripe

Request

  • Base URL: https://api.faireplace.com/api
  • URL: https://api.faireplace.com/api/webhooks/stripe
  • Auth: none required

Headers

Stripe-Signaturestring required

Stripe signature to verify the authenticity of the webhook.

Format: t=<timestamp>,v1=<signature>

This signature is computed by Stripe using HMAC SHA256 and the webhook secret. It ensures that the webhook genuinely comes from Stripe.

Request body

idstring required

Unique identifier of the Stripe event (starts with evt_).

Used for idempotency and traceability.

typestring required

Stripe event type.

Supported types:

  • checkout.session.completed: Payment via Checkout Session completed
  • payment_intent.succeeded: PaymentIntent confirmed successfully
  • payment_intent.payment_failed: Payment failed

Example request

{
  "id": "evt_test_123",
  "type": "checkout.session.completed"
}

Response

Webhook received and processed successfully.

Always returns 200 OK even for unhandled events, per Stripe recommendations.

receivedboolean required

Indicates that the webhook has been received.

Always true on success.

Example response

{
  "received": true
}

Changes

No recorded changes to this operation across all 1 revision of this API.