# Stripe Webhooks

Handle payment events in real-time using Stripe webhooks. FairePlace uses Stripe for credit purchases, and you can listen for webhook events to track payment status in your system.

## Setup

1. Go to your [Stripe Dashboard](https://dashboard.stripe.com/webhooks)
2. Add a webhook endpoint pointing to your server
3. Select the events you want to receive
4. Save the webhook signing secret

## Key events

| Event | Description | Action |
|-------|-------------|--------|
| `checkout.session.completed` | Payment successful | Credits added to balance |
| `charge.succeeded` | Charge confirmed by bank | Payment finalized |
| `charge.refunded` | Refund processed | Credits deducted from balance |
| `charge.failed` | Payment failed | No credits added |

## Webhook payload

```json
{
  "id": "evt_1234567890",
  "type": "checkout.session.completed",
  "data": {
    "object": {
      "id": "cs_live_a1b2c3d4e5f6g7h8",
      "payment_status": "paid",
      "amount_total": 25000,
      "currency": "eur",
      "metadata": {
        "tenant_id": "550e8400-e29b-41d4-a716-446655440000",
        "package": "10_credits",
        "credits": "10"
      }
    }
  },
  "created": 1708425600
}
```

## Verify webhook signatures

Always verify the Stripe signature to ensure the event is authentic and hasn't been tampered with:

### Node.js

```javascript
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);

app.post("/webhooks/stripe", express.raw({ type: "application/json" }), (req, res) => {
  const sig = req.headers["stripe-signature"];

  let event;
  try {
    event = stripe.webhooks.constructEvent(
      req.body,
      sig,
      process.env.STRIPE_WEBHOOK_SECRET
    );
  } catch (err) {
    console.error("Webhook signature verification failed:", err.message);
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  switch (event.type) {
    case "checkout.session.completed":
      const session = event.data.object;
      console.log(`Credits purchased: ${session.metadata.credits} for tenant ${session.metadata.tenant_id}`);
      // Update your local records
      break;

    case "charge.refunded":
      const charge = event.data.object;
      console.log(`Refund processed: ${charge.amount_refunded / 100} EUR`);
      // Deduct credits from local records
      break;
  }

  res.json({ received: true });
});
```

### Python

```python
import stripe
from flask import Flask, request, jsonify

stripe.api_key = os.environ["STRIPE_SECRET_KEY"]
endpoint_secret = os.environ["STRIPE_WEBHOOK_SECRET"]

@app.route("/webhooks/stripe", methods=["POST"])
def stripe_webhook():
    payload = request.get_data()
    sig = request.headers.get("Stripe-Signature")

    try:
        event = stripe.Webhook.construct_event(payload, sig, endpoint_secret)
    except stripe.error.SignatureVerificationError:
        return "Invalid signature", 400

    if event["type"] == "checkout.session.completed":
        session = event["data"]["object"]
        credits = int(session["metadata"]["credits"])
        tenant_id = session["metadata"]["tenant_id"]
        # Update your local records

    return jsonify({"received": True})
```

## Retry policy

Stripe retries webhook deliveries for up to 3 days with exponential backoff:

| Attempt | Delay |
|---------|-------|
| 1st retry | 5 minutes |
| 2nd retry | 30 minutes |
| 3rd retry | 2 hours |
| 4th retry | 8 hours |
| 5th retry | 1 day |

Your endpoint should return a `2xx` status code within 20 seconds. If it returns an error or times out, Stripe will retry.

## Best practices

1. **Always verify signatures** — Never trust webhook payloads without verifying the Stripe signature
2. **Return 200 quickly** — Process events asynchronously if they require heavy computation
3. **Handle duplicates** — Stripe may send the same event more than once. Use the event `id` to deduplicate
4. **Use the metadata** — FairePlace includes `tenant_id`, `package`, and `credits` in the session metadata
5. **Monitor failures** — Set up alerts in Stripe Dashboard for failed webhook deliveries

## Testing webhooks locally

Use the Stripe CLI to forward webhook events to your local server:

```bash
# Install Stripe CLI and login
stripe listen --forward-to localhost:3000/webhooks/stripe

# Trigger a test event
stripe trigger checkout.session.completed
```

---

## Related

- **[Payments & Credits](/payments/credits)** — Purchase credits and check balance
- **[Electronic Signatures](/signatures/overview)** — How credits are consumed
- **[Error Handling](/core/errors)** — API error codes
