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

# Create your webhook endpoint

Build a webhook endpoint to receive and process events from Fintoc. A webhook endpoint works like any other application endpoint, with additional delivery and retry considerations.

Review [Webhook best practices](/guides/resources/webhooks-walkthrough/webhooks-good-practices) before implementing your endpoint.

## Key considerations

When an event occurs, Fintoc sends a `POST` request with a JSON body to every registered webhook endpoint. Parse the JSON body to access the event details.

Your webhook endpoint must parse the `POST` request and return a status code in the `2xx` range.

### Save the received event

Every webhook event sent by Fintoc has a unique `id`. Save each event `id` in your application's database. Use the saved `id` to detect duplicate deliveries and process each event only once.

### Return a `2xx` status code before the timeout

To confirm receipt of an event, respond with a `2xx` HTTP status code. Fintoc treats any other status code as a failed attempt.

Respond with a `2xx` status code before Fintoc's delivery timeout. Run logic that does not need to complete before acknowledgment asynchronously so the handler returns before the timeout.

### Handle retries and failed deliveries

Fintoc treats a delivery as a **failed attempt** if your endpoint responds with any status code outside the `2xx` range or if the request times out.

When a delivery fails, Fintoc automatically retries it using **exponential backoff**, starting 3 seconds after the failed attempt. Fintoc makes **up to 17 retry attempts** for each event.

If every attempt fails, Fintoc stops retrying that event. The following limitations apply:

* Fintoc does **not** disable the webhook endpoint automatically or notify you when it stops retrying an event.
* Fintoc does **not** support manual replay or a dead-letter mechanism to resend a failed event.

Fintoc can deliver the **same event more than once**, so your endpoint must be idempotent. To handle duplicate events, see [Avoid event duplication](/guides/resources/webhooks-walkthrough/webhooks-good-practices#avoid-event-duplication).

### Test the webhook

Fintoc sends webhook events only when the underlying action occurs. A handler bug can therefore go unnoticed until the endpoint receives production traffic. Test your endpoint when you create it, register it, or change how it receives events.

For the step-by-step test procedure, including how to expose your local endpoint and send a test event, see [Test your webhook endpoint](/guides/resources/webhooks-walkthrough/webhooks-testing).

## Sample code

**Webhook handler (Python)**

```python theme={null}
import json

from flask import Flask, jsonify, request

app = Flask(__name__)

@app.route('/webhook', methods=['POST'])
def webhook():
    payload = request.data
    try:
        event = json.loads(payload)
    except (json.JSONDecodeError, UnicodeDecodeError):
        return jsonify(received=False), 400

    if not isinstance(event, dict) or 'type' not in event:
        return jsonify(received=False), 400

    # Add idempotency with your app's ORM.

    # Handle the event
    if event['type'] == 'link.credentials_changed':
        link_id = event['data']['id']
        # Then define and call a method to handle the credentials changed event.
    elif event['type'] == 'link.refresh_intent.succeeded':
        link_id = event['data']['refreshed_object_id']
        # Then define and call a method to handle the link refreshed event.
    elif event['type'] == 'account.refresh_intent.succeeded':
        account_id = event['data']['refreshed_object_id']
        # Then define and call a method to handle the account refreshed event.
    # ... handle other event types
    else:
        # Unexpected event type
        print('Unhandled event type {}'.format(event['type']))
    return jsonify(received=True)
```

**Webhook handler (Node.js)**

```javascript theme={null}
// This example uses Express to receive webhooks
const app = require('express')();

// Use body-parser to retrieve the raw body as a buffer
const bodyParser = require('body-parser');

app.post('/webhook', bodyParser.raw({ type: 'application/json' }), (request, response) => {
  let event;
  try {
    event = JSON.parse(request.body);
  } catch {
    return response.status(400).json({ received: false });
  }

  if (!event || typeof event !== 'object' || typeof event.type !== 'string') {
    return response.status(400).json({ received: false });
  }

  // Add idempotency with your app's ORM.

  // Handle the event
  switch (event.type) {
    case 'link.credentials_changed': {
      const linkId = event.data.id;
      // Define and call a method to handle the credentials changed event.
      break;
    }
    case 'link.refresh_intent.succeeded': {
      const linkId = event.data.refreshed_object_id;
      // Define and call a method to handle the link refreshed event.
      break;
    }
    case 'account.refresh_intent.succeeded': {
      const accountId = event.data.refreshed_object_id;
      // Define and call a method to handle the account refreshed event.
      break;
    }
    // ... handle other event types
    default:
      // Unexpected event type
      console.log(`Unhandled event type ${event.type}`);
  }

  // Return a response to acknowledge receipt of the event
  response.json({ received: true });
});

app.listen(8000, () => console.log('Running on port 8000'));
```
