Webhooks
OpenScouter receives webhooks from Stripe for payment and payout events, and provides an OAuth callback endpoint for authentication flows.
Stripe Webhook Security
All incoming Stripe payloads are verified using the Stripe signature header before processing. Requests without a valid Stripe-Signature header return 401. Configure your Stripe webhook endpoint in the Stripe Dashboard with the webhook secret stored in STRIPE_WEBHOOK_SECRET.
Endpoints
/api/webhooks/stripe Webhook Receive and process Stripe payment events
Stripe calls this endpoint for account updates and payout outcomes. You do not call this endpoint directly.
Headers required by Stripe
Stripe-Signature: t=1678901234,v1=abc123...Content-Type: application/jsonSupported event types
| Event | Trigger |
|---|---|
account.updated | A connected account’s details changed (e.g., verification status) |
payout.paid | A payout was successfully delivered to a tester’s bank account |
payout.failed | A payout failed and was not delivered |
Example payload: payout.paid
{ "id": "evt_001", "type": "payout.paid", "created": 1710590400, "data": { "object": { "id": "po_001", "amount": 1500, "currency": "usd", "arrival_date": 1710676800, "status": "paid", "destination": "ba_001" } }}Example payload: payout.failed
{ "id": "evt_002", "type": "payout.failed", "created": 1710590500, "data": { "object": { "id": "po_002", "amount": 1500, "currency": "usd", "status": "failed", "failure_code": "insufficient_funds", "failure_message": "The bank account has insufficient funds to complete the payout." } }}Example payload: account.updated
{ "id": "evt_003", "type": "account.updated", "created": 1710590600, "data": { "object": { "id": "acct_001", "charges_enabled": true, "payouts_enabled": true, "details_submitted": true, "requirements": { "currently_due": [], "eventually_due": [] } } }}Response
Return 200 OK to acknowledge receipt. Any other status triggers Stripe retry logic with exponential backoff.
{ "received": true }Internal processing
| Event | Action taken |
|---|---|
account.updated | Updates the tester’s Stripe account status in the database |
payout.paid | Marks the associated session payment as paid, notifies the tester |
payout.failed | Marks the payment as failed, triggers admin alert, queues retry |
/api/auth/callback Handle the OAuth authorization code callback
This endpoint completes the OAuth flow after a user approves the authorization request on an external provider (Google, GitHub, etc.). The provider redirects here with a short-lived authorization code.
Query parameters
| Parameter | Description |
|---|---|
code | Authorization code from the OAuth provider |
state | CSRF state token generated at the start of the OAuth flow |
error | Error code if the user denied access (e.g., access_denied) |
Successful flow
- OpenScouter exchanges
codefor access and refresh tokens. - The user record is created or updated.
- A session JWT is issued.
- The user is redirected to the post-login destination.
Example success redirect
GET /api/auth/callback?code=4/abc123&state=xyz789Redirects to https://openscouter.com/dashboard with a Set-Cookie header containing the session JWT.
Example error redirect
GET /api/auth/callback?error=access_denied&state=xyz789Redirects to https://openscouter.com/login?error=access_denied.
Error responses
| Scenario | Response |
|---|---|
Missing or invalid state | 400 Bad Request — CSRF check failed |
| Code exchange fails | 401 Unauthorized — provider rejected the code |
| Provider account banned | 403 Forbidden — account is suspended |