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:
- Extract the timestamp and v1 signature from the Stripe-Signature header
- Build the signed payload: timestamp + "." + payload_body
- Compute HMAC SHA256 with the webhook secret (STRIPE_WEBHOOK_SECRET)
- Constant-time comparison with the received signature
- 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:
- Extract metadata (tenant_id, user_id, package_id)
- Idempotency check (avoid double processing)
- Retrieve the package from DB via stripe_price_id
- Credit the user's account
- 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:
- Go to Developers → Webhooks
- Click Add endpoint
- URL: https://api.faireplace.com/api/webhooks/stripe (or dev URL for testing)
- Select the events:
- ✅ checkout.session.completed
- ✅ payment_intent.succeeded
- ✅ payment_intent.payment_failed
- 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:
- The error is logged with all details
- A 500 response is returned to Stripe
- 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.
Request
- Base URL: https://api.faireplace.com/api
- URL: https://api.faireplace.com/api/webhooks/stripe
- Auth: none required
Headers
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
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.
Example response
{
"received": true
}Changes
No recorded changes to this operation across all 1 revision of this API.