> For the complete documentation index, see [llms.txt](https://docs.bullcredtech.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.bullcredtech.com/api-reference/apis-en/guias/partner-auction-webhook.md).

# Auction Webhook

This page describes how to receive proposals from the **Private Payroll Loan Auction** from Bull via webhook.

{% hint style="warning" %}
**Restricted-access document**

This page is not listed in the public documentation menu. The link should only be shared with partners formally authorized to receive auction proposals.
{% endhint %}

## Overview

Whenever Bull's auction generates an eligible proposal for your business, we send a `POST` real-time HTTP request to the endpoint you provide, with the minimum data needed for you to present the offer to the client.

```mermaid
sequenceDiagram
    participant Bull as Bull (Auction)
    participant Partner as Your endpoint

    Bull->>Bull: Proposal generated and validated
    Bull->>Partner: POST /webhook (JSON payload)
    Partner-->>Bull: HTTP 2xx
    Note over Bull,Partner: In case of failure, Bull automatically retries
```

You only receive proposals that have already passed the previously agreed eligibility rules. You do not need to poll or call Bull's API to discover new proposals — they arrive at your endpoint as they are generated.

## Notification Format

`POST {YOUR_ENDPOINT_URL}`

### Headers

| Header              | Value                                                                                                                                                         |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Content-Type`      | `application/json`                                                                                                                                            |
| `X-Idempotency-Key` | Deterministic key (SHA-256 in hex) that uniquely identifies the proposal. **Always sent.** The same proposal — including retries — arrives with the same key. |
| `x-api-key`         | API key that you provide us (optional — only sent if configured)                                                                                              |

{% hint style="info" %}
**`X-Idempotency-Key` is the official deduplication key**

The key is derived deterministically from the proposal's internal identifier and the material fields (`document`, `available_balance`, `installment_quantity`). Use **this header**, and not a combination of body fields, as your internal deduplication key — it is simpler, cheaper, and future-proof against changes in the payload.
{% endhint %}

### Body

```json
{
  "document": "11122233344",
  "name": "Client Name",
  "available_balance": "2500.5",
  "installment_quantity": 24
}
```

### Fields

| Field                  | Type             | Description                              |
| ---------------------- | ---------------- | ---------------------------------------- |
| `document`             | string           | Client CPF (11 digits, no mask)          |
| `name`                 | string           | Client's full name                       |
| `available_balance`    | string (decimal) | Available balance for the loan, in reais |
| `installment_quantity` | integer          | Number of installments in the offer      |

## What you need to provide us

To start receiving proposals, send the Bull team the following information:

| Item                                                 | Required           | Description                                                                                                                                                |
| ---------------------------------------------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Staging webhook URL**                              | Yes                | Public HTTPS endpoint for testing before going to production                                                                                               |
| **Production webhook URL**                           | Yes                | Public HTTPS endpoint that will receive the real proposals                                                                                                 |
| **API key (`x-api-key`)**                            | Optional           | Token that we will send in the header `x-api-key` for you to authenticate the request. If you do not provide it, the `POST` is sent without authentication |
| **Outgoing IPs to allow (if there is an allowlist)** | Yes, if applicable | If your firewall blocks by IP, ask the Bull team for the fixed outgoing IPs used for sending                                                               |
| **Technical contact for incidents**                  | Yes                | Email / Slack / phone to contact in case of instability in receiving                                                                                       |

Both the URL and the API key can be **changed or rotated at any time** — just inform the Bull team. The update is made on our side and takes effect in subsequent sends, without you needing to change anything in your infrastructure.

## What you need to implement on your side

### Endpoint requirements

* Accept `POST` with `Content-Type: application/json`.
* Be available over **HTTPS** with a valid certificate (not self-signed).
* Respond with **HTTP 2xx** within **25 seconds**. Any other status (3xx, 4xx, 5xx), timeout or network error is considered **failure** and triggers the retry cycle.
* Implement **idempotency**: the same proposal may arrive more than once in retry situations. You must silently discard duplicates, responding `2xx` even when the proposal has already been processed internally. Use the header `X-Idempotency-Key` as the deduplication key.
* Validate the header `x-api-key`, if configured.

{% hint style="success" %}
**Why idempotency matters**

Bull guarantees "at-least-once delivery" — not "exactly-once". In retries or operational redeliveries, the same payload may arrive two or more times. Use the value of the header `X-Idempotency-Key` (SHA-256 hex string) as your internal deduplication key — Bull guarantees that the same logical proposal always carries the same value, and that distinct proposals carry different values.
{% endhint %}

### Expected response

Any response body with status `2xx` is considered success and Bull stops sending that proposal. If you want to return a generated internal ID, you may include it in the body — Bull does not consume the response content, only the HTTP status.

### Best practices

* **Respond quickly.** Persist the lead in a queue/database and return `2xx` immediately. Do not run credit rules, external queries, or heavy processing in the same request — you risk exceeding 25s and causing unnecessary retries.
* **Always 2xx for proposals you decide not to take.** If you receive a payload that does not meet your internal rules, respond `2xx` and discard it internally. Responding 4xx will make Bull try to resend up to 5 times.
* **Monitor latency and error rate** on your endpoint. Persistent failures trigger alerts on our side and the proposal may be discarded for operational analysis after retries are exhausted.

## Retry and Fault Tolerance

When the `POST` to your endpoint fails, Bull automatically tries to resend. The full retry cycle takes about **25 minutes** before the proposal leaves the automatic flow.

| Attempt      | When                              | What happens                                                       |
| ------------ | --------------------------------- | ------------------------------------------------------------------ |
| 1st          | Immediate                         | `POST` to your endpoint                                            |
| 2nd          | \~5 minutes after the 1st failure | New attempt                                                        |
| 3rd          | \~10 minutes after the 1st        | New attempt                                                        |
| 4th          | \~15 minutes after the 1st        | New attempt                                                        |
| 5th          | \~20 minutes after the 1st        | Final automatic attempt                                            |
| End of retry | \~25 minutes after the 1st        | Proposal leaves the automatic flow and enters operational analysis |

### Send parameters

| Parameter                             | Value            |
| ------------------------------------- | ---------------- |
| HTTP timeout per attempt              | **25 seconds**   |
| Approximate interval between attempts | **5 minutes**    |
| Maximum number of attempts            | **5**            |
| Total retry window                    | **\~25 minutes** |

### Failure scenarios

| Your endpoint response          | What Bull does                                |
| ------------------------------- | --------------------------------------------- |
| `HTTP 200`—`299`                | Success. The proposal is marked as delivered. |
| `HTTP 4xx` (400, 401, 422 etc.) | Failure. New attempt in \~5 minutes.          |
| `HTTP 5xx` (500, 502, 503, 504) | Failure. New attempt in \~5 minutes.          |
| Timeout (> 25s)                 | Failure. New attempt in \~5 minutes.          |
| DNS error / connection refused  | Failure. New attempt in \~5 minutes.          |
| Invalid HTTPS certificate       | Failure. New attempt in \~5 minutes.          |

{% hint style="info" %}
**4xx also trigger retry**

Bull does not distinguish between 4xx and 5xx errors for retry purposes: any status outside the 2xx range triggers a new attempt. That's why we recommend responding `2xx` for leads you decided to discard — this way you avoid 5 unnecessary redeliveries.
{% endhint %}

### What happens after retries are exhausted

If the 5 attempts fail, the proposal leaves the automatic sending flow and is recorded for analysis by Bull's operations team. Common cases:

* **Momentary instability of your endpoint** → the Bull team can resend manually after you confirm that the issue has been resolved.
* **Changing the URL or API key without informing Bull** → we update the configuration and resend the pending proposals.
* **Payload incompatible with your API** → we review the contract together.

In any case, **there is no silent loss**: every proposal that fails all attempts generates an internal alert and is handled manually by the Bull team.

## Onboarding Flow

To start receiving proposals:

1. **Send the Bull team** the data listed in [What you need to provide us](#o-que-voce-precisa-nos-fornecer).
2. **Implement the endpoint** following the [requirements](#requisitos-do-endpoint).
3. **Staging tests**: Bull sends test proposals to your staging endpoint. You validate receipt, API key authentication, and idempotency.
4. **Go-live in production**: after validation in staging, Bull enables production sending.
5. **Continuous operation**: you monitor your endpoint; Bull monitors sending and contacts the technical contact in case of instability.

## Implementation Example

### Node.js (Express)

```javascript
const express = require('express');
const app = express();

app.use(express.json());

app.post('/bull-auction-webhook', async (req, res) => {
  // 1. Validate API key
  if (req.headers['x-api-key'] !== process.env.BULL_AUCTION_API_KEY) {
    return res.status(401).json({ error: 'unauthorized' });
  }

  const { document, name, available_balance, installment_quantity } = req.body;

  // 2. Idempotency — use the X-Idempotency-Key header sent by Bull
  const idempotencyKey = req.headers['x-idempotency-key'];
  if (!idempotencyKey) {
    return res.status(400).json({ error: 'missing X-Idempotency-Key' });
  }
  if (await alreadyProcessed(idempotencyKey)) {
    return res.status(200).json({ duplicate: true });
  }

  // 3. Persist and return 2xx quickly — do not do heavy work here
  await enqueueLeadForProcessing({ document, name, available_balance, installment_quantity });
  await markProcessed(idempotencyKey);

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

### Python (Flask)

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

app = Flask(__name__)

@app.route('/bull-auction-webhook', methods=['POST'])
def auction_webhook():
    if request.headers.get('x-api-key') != os.environ['BULL_AUCTION_API_KEY']:
        return jsonify(error='unauthorized'), 401

    idempotency_key = request.headers.get('X-Idempotency-Key')
    if not idempotency_key:
        return jsonify(error='missing X-Idempotency-Key'), 400

    if already_processed(idempotency_key):
        return jsonify(duplicate=True), 200

    data = request.json
    enqueue_lead(data)
    mark_processed(idempotency_key)

    return jsonify(received=True), 200
```

## Contact

For questions, configuration changes (URL, API key, eligibility policy) or production incidents, contact the Bull team through the integration channel defined during onboarding.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.bullcredtech.com/api-reference/apis-en/guias/partner-auction-webhook.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
