> ## Documentation Index
> Fetch the complete documentation index at: https://docs.finosu.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhook quickstart

> Receive and verify your first signed test event.

This walkthrough gets a draft endpoint receiving `webhook.test`. You will need
Python 3.10 or later, a public HTTPS URL that reaches your receiver, and Finosu to
configure the integration. Endpoint management currently requires internal staff
access; no public API-key endpoint creation API is available.

## 1. Prepare your receiver

Copy the verifier from [Verify signatures](https://docs.finosu.com/api-reference/webhooks/verification.md)
into `verification.py`. Save this alongside it as `receiver.py`:

```python theme={null}
import json
import os

from fastapi import FastAPI, Request, Response

from verification import verify_finosu_webhook

MAX_BODY_BYTES = 64 * 1024
app = FastAPI()
secret = os.environ["FINOSU_WEBHOOK_SECRET"]
company_id = os.environ["FINOSU_COMPANY_ID"]
environment = os.environ["FINOSU_WEBHOOK_ENVIRONMENT"]


@app.post("/webhooks/finosu")
async def receive_webhook(request: Request) -> Response:
    declared_length = request.headers.get("content-length")
    if declared_length is not None:
        try:
            length = int(declared_length)
        except ValueError:
            return Response(status_code=400)
        if length < 0:
            return Response(status_code=400)
        if length > MAX_BODY_BYTES:
            return Response(status_code=413)
    raw_body = bytearray()
    async for chunk in request.stream():
        if len(raw_body) + len(chunk) > MAX_BODY_BYTES:
            return Response(status_code=413)
        raw_body.extend(chunk)
    raw_body = bytes(raw_body)
    signature = request.headers.get("X-Finosu-Signature", "")
    if not verify_finosu_webhook(raw_body, signature, secret):
        return Response(status_code=401)

    try:
        event = json.loads(raw_body)
    except (ValueError, UnicodeDecodeError):
        return Response(status_code=400)

    if not isinstance(event, dict):
        return Response(status_code=400)
    if (
        event.get("company_id") != company_id
        or event.get("environment") != environment
    ):
        return Response(status_code=400)

    if event.get("type") == "webhook.test" and event.get("test") is True:
        return Response(status_code=204)

    # This setup receiver has no business-event storage or processing yet.
    return Response(status_code=503)
```

This receiver acknowledges signed tests only. It returns `503` for business
events so they are not silently discarded. Keep the endpoint in draft until you
implement [durable acceptance and processing](https://docs.finosu.com/api-reference/webhooks/receiving.md).

## 2. Configure a draft endpoint

Give Finosu:

| Setting | Example |
| - | - |
| Name | Payment sync |
| Receiver URL | `https://hooks.example.com/webhooks/finosu` |
| Event subscriptions | `payment.succeeded`, `payment.failed`, `payment.returned` |
| Company and environment | The company and deployment this integration serves. |

Choose subscriptions from the [event catalog](https://docs.finosu.com/api-reference/webhooks/events.md).
Finosu creates the draft endpoint and provides its signing secret, company UUID,
and exact environment value. Save the secret when it is provided.

The destination must use public HTTPS on port 443. Private addresses, localhost,
embedded URL credentials, URL fragments, and redirects are not supported. For
local development, use a public HTTPS tunnel to your local receiver. The example
path `/webhooks/finosu` belongs to your server, not `api.finosu.com`.

## 3. Run the receiver

Install the dependencies in a virtual environment:

```bash theme={null}
python3 -m venv .venv
source .venv/bin/activate
python -m pip install fastapi uvicorn
```

Set `FINOSU_WEBHOOK_SECRET`, `FINOSU_COMPANY_ID`, and
`FINOSU_WEBHOOK_ENVIRONMENT` in your server environment using the values Finosu
provided. Do not commit them or put them in client-side code. Then run:

```bash theme={null}
python -m uvicorn receiver:app --host 127.0.0.1 --port 8081
```

Before exposing the receiver, configure your ingress with a 64 KiB request-body
limit and a request timeout. The sample also bounds streamed bodies, including
requests without `Content-Length`. Route your public HTTPS URL to this server. If an optional bearer token was
configured, also validate its `Authorization` header at your receiver or gateway;
this example uses signature authentication only.

## 4. Receive a test

Ask Finosu to send a test from the draft endpoint. A successful setup has three
observable results:

1. Your receiver gets a `POST /webhooks/finosu` with `X-Finosu-Signature`.
2. Signature and scope checks pass, and your receiver responds with `204`.
3. Finosu's delivery view shows an accepted test for the current configuration.

The event has `type: "webhook.test"`, `test: true`, and null customer and loan
references. See the [test payload](https://docs.finosu.com/api-reference/webhooks/events.md#webhooktest).
A successful test verifies connectivity and signing; it does not validate your
business-event processing or activate the endpoint.

## 5. Handle business events, then activate

Replace the sample's `503` branch with handling for your subscribed events:

1. Validate the event's fields against the [catalog](https://docs.finosu.com/api-reference/webhooks/events.md).
2. Atomically store the event and enqueue work, deduplicating by company and event
   ID. Already accepted duplicates should also receive `2xx`.
3. Return `200` or `204` promptly. Process the queued event in your worker.

Deploy to a stable public HTTPS URL, configure a request-body size limit suitable
for your subscribed payloads, and verify both a duplicate delivery and recovery
from a temporary storage failure. Read [delivery and retries](https://docs.finosu.com/api-reference/webhooks/receiving.md)
for the retry budget, ordering, and manual recovery rules.

When the receiver is ready, have Finosu activate the verified endpoint. It starts
capturing subsequent subscribed events; activation does not backfill history.
Changing the URL, credentials, or subscriptions requires a new test before
activation.

## Troubleshooting

| Result | Next step |
| - | - |
| No request arrives | Check public HTTPS routing and the exact destination path with Finosu. |
| `401` | Check the endpoint secret, raw request bytes, signature header, and receiver clock. |
| `400` | Check JSON validity, the configured company UUID, and environment. |
| `503` from this example | A business event reached the test-only receiver. Finish durable handling before activation. |
| Test accepted but endpoint inactive | Ask Finosu to activate once business-event handling is ready. |

Fixing a `400` or `401` does not automatically retry that failed delivery. Request
a fresh test after the fix. See [signature troubleshooting](https://docs.finosu.com/api-reference/webhooks/verification.md#troubleshooting)
for verification details.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.