FairePlace Stripe API

Stripe-facing endpoints (webhooks and checkout plumbing).

GitHub OpenAPI

Operations 1

POST /webhooks/stripe Stripe Webhook - Event reception #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/faireplace-com:faireplace-com-stripe-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

faireplace-com-stripe-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: FairePlace Property Management Stripe API
  description: 'Comprehensive API for managing properties, leases, tenants, and all aspects of real estate management.

    Built with Rust and Actix-web following hexagonal architecture principles.


    ## Authentication

    This API uses API keys for authentication with tenant-based isolation.


    ## Base URL

    - Production: `https://api.faireplace.com/api`'
  version: 1.0.0
  contact:
    name: FairePlace API Support
    email: api-support@faireplace.com
  license:
    name: MIT
    url: https://opensource.org/licenses/MIT
servers:
- url: https://api.faireplace.com/api
  description: Production server
security:
- BearerAuth: []
tags:
- name: Stripe
  description: Stripe-facing endpoints (webhooks and checkout plumbing).
paths:
  /webhooks/stripe:
    post:
      operationId: stripeWebhook
      summary: Stripe Webhook - Event reception
      description: '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**:

        ```json

        {

        "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


        ```bash

        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**:

        ```json

        {

        "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.'
      tags:
      - Stripe
      security: []
      requestBody:
        required: true
        description: "Stripe webhook payload in JSON format.\n\nThe exact format depends on the event type, but all Stripe webhooks\nfollow the standard structure:\n\n```json\n{\n  \"id\": \"evt_xxx\",\n  \"type\": \"checkout.session.completed\",\n  \"data\": {\n    \"object\": { ... }\n  }\n}\n```\n"
        content:
          application/json:
            schema:
              type: object
              description: 'Stripe Webhook Event.


                The exact structure depends on the event type.

                See the Stripe documentation for full details.

                '
              required:
              - id
              - type
              - data
              properties:
                id:
                  type: string
                  description: 'Unique identifier of the Stripe event (starts with `evt_`).


                    Used for idempotency and traceability.

                    '
                  example: evt_test_123
                type:
                  type: string
                  description: '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: checkout.session.completed
                data:
                  type: object
                  description: 'Event data.


                    Contains the relevant Stripe object (Checkout Session, PaymentIntent, etc.)

                    in the `object` property.

                    '
                  required:
                  - object
                  properties:
                    object:
                      type: object
                      description: 'Stripe object related to the event.


                        For `checkout.session.completed`, contains the Checkout Session.

                        For `payment_intent.succeeded`, contains the PaymentIntent.

                        '
            examples:
              checkout_session_completed:
                summary: Checkout Session completed
                value:
                  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:
                summary: PaymentIntent succeeded
                value:
                  id: evt_test_456
                  type: payment_intent.succeeded
                  data:
                    object:
                      id: pi_test_123
                      status: succeeded
                      amount: 2990
                      currency: eur
      parameters:
      - name: Stripe-Signature
        in: header
        required: true
        description: '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.

          '
        schema:
          type: string
        example: t=1234567890,v1=abc123def456...
      responses:
        '200':
          description: 'Webhook received and processed successfully.


            Always returns 200 OK even for unhandled events,

            per Stripe recommendations.

            '
          content:
            application/json:
              schema:
                type: object
                required:
                - received
                properties:
                  received:
                    type: boolean
                    description: 'Indicates that the webhook has been received.


                      Always `true` on success.

                      '
                    example: true
              examples:
                success:
                  summary: Webhook processed successfully
                  value:
                    received: true
        '400':
          description: 'Invalid request.


            **Possible causes**:

            - Invalid JSON payload

            - Missing required fields (`id`, `type`, `data.object`)

            - Missing metadata in Checkout Session (`tenant_id`, `user_id`)

            - Unsupported event format

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                invalid_payload:
                  summary: Invalid payload
                  value:
                    error:
                      code: PARSE_ERROR
                      message: 'Failed to parse webhook: invalid json'
                      status: 400
                missing_metadata:
                  summary: Missing metadata
                  value:
                    error:
                      code: VALIDATION_ERROR
                      message: Missing tenant_id in metadata
                      status: 400
        '401':
          description: 'Invalid or missing signature.


            **Possible causes**:

            - Missing `Stripe-Signature` header

            - Invalid signature format

            - Signature does not match the payload

            - Timestamp too old (> 5 minutes)


            ⚠️ **Security**: Webhooks without a valid signature are always rejected.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                missing_signature:
                  summary: Missing signature
                  value:
                    error:
                      code: UNAUTHORIZED
                      message: Missing Stripe-Signature header
                      status: 401
                invalid_signature:
                  summary: Invalid signature
                  value:
                    error:
                      code: UNAUTHORIZED
                      message: Invalid Stripe signature
                      status: 401
                timestamp_too_old:
                  summary: Timestamp too old
                  value:
                    error:
                      code: UNAUTHORIZED
                      message: 'Timestamp too old or too far in future: 600 seconds difference'
                      status: 401
        '500':
          description: 'Internal error during webhook processing.


            **Possible causes**:

            - Error retrieving the package

            - Error adding credits

            - Database error


            ⚠️ **Note**: Stripe will automatically retry webhooks that return 500

            (up to 3 attempts over 3 days).

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    Error:
      type: object
      description: "The error envelope returned by every authenticated endpoint.\n\nTwo things are worth knowing before you write your error handling:\n\n- **`code` is a symbolic string, not a number.** The numeric HTTP status is\n  in `status`. Branch on `code`: it is stable, and show `message`, which\n  is written for humans and may be reworded without notice.\n- The public guest-signature endpoints use a **different, flatter shape**\n  (`{ \"error_code\": …, \"message\": … }`, no `error` wrapper), because they\n  are consumed by a page that has no session. Their responses document it\n  inline.\n\nThe `X-Request-Id` response header carries the request identifier: quote it\nwhen reporting a problem. It is a header, not a body field.\n"
      required:
      - error
      properties:
        error:
          type: object
          required:
          - code
          - message
          - status
          properties:
            code:
              type: string
              description: Stable symbolic code. Branch on this.
              enum:
              - NOT_FOUND
              - UNAUTHORIZED
              - VALIDATION_ERROR
              - DATABASE_ERROR
              - BUSINESS_ERROR
              - STORAGE_ERROR
              - CONFIG_ERROR
              - FORBIDDEN
              - CONFLICT
              - PARSE_ERROR
              - SERIALIZATION_ERROR
              - AUTHENTICATION_ERROR
              - AUTHORIZATION_ERROR
              - EXTERNAL_SERVICE_ERROR
              - RATE_LIMIT_EXCEEDED
              - PAYMENT_REQUIRED
              - PLAN_LIMIT_REACHED
              - GONE
              - BAD_REQUEST
              - INTERNAL_ERROR
              - INTERNAL_SERVER_ERROR
              example: NOT_FOUND
            message:
              type: string
              description: 'Human-readable explanation, most often in French. Safe to display,

                not safe to parse.

                '
              example: Lease not found
            status:
              type: integer
              description: The HTTP status code, repeated in the body.
              example: 404
            details:
              type: object
              additionalProperties: true
              description: 'Extra machine-readable context. Only present on some errors: on

                `RATE_LIMIT_EXCEEDED` it carries `retry_after_seconds`.

                '
              example:
                retry_after_seconds: 12
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: API key with fp_* prefix, passed as Bearer token
    WorkerApiKey:
      type: apiKey
      in: header
      name: X-API-Key
    WorkerBearer:
      type: http
      scheme: bearer
      bearerFormat: Worker