> 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/webhooks.md).

# Webhooks

Bull sends notifications via webhook when a proposal's status changes. This allows partners to track proposal progress in real time, without the need for polling.

## Overview

When a proposal status changes (e.g. from `SIGNED` to `DISBURSED`), Bull sends an HTTP POST to the endpoint configured by the partner with the details of the change.

```mermaid
sequenceDiagram
    participant Bull as Bull API
    participant SNS as AWS SNS
    participant SQS as AWS SQS
    participant Lambda as Webhook Sender
    participant Partner as Partner

    Bull->>SNS: Status changed
    SNS->>SQS: Message queued
    SQS->>Lambda: Process message
    Lambda->>Partner: POST /webhook
    Partner-->>Lambda: 200 OK
```

## Notification Format

```json
{
  "origin": {
    "code": "ORIGIN_CODE",
    "name": "Origin Name"
  },
  "timestamp": "2025-10-26T19:44:36.950051+00:00",
  "event_type": "proposal.status.changed",
  "proposal_id": "c3874d5f-77cf-471d-91b6-6fab27abea7e",
  "proposal_data": {
    "cpf": "42351078608",
    "phone": "+5511991818067",
    "created_at": "2025-10-26T16:33:02.725082",
    "updated_at": "2025-10-26T19:44:36.950051"
  },
  "status_change": {
    "new_status": "TOKEN_VALIDATED",
    "old_status": "TOKEN_SENT"
  }
}
```

### Fields

| Field                      | Type     | Description                                 |
| -------------------------- | -------- | ------------------------------------------- |
| `origin.code`              | string   | Originator code                             |
| `origin.name`              | string   | Originator name                             |
| `timestamp`                | ISO 8601 | Status change date/time                     |
| `event_type`               | string   | Event type (e.g. `proposal.status.changed`) |
| `proposal_id`              | UUID     | Proposal ID                                 |
| `proposal_data.cpf`        | string   | Customer's CPF (11 digits)                  |
| `proposal_data.phone`      | string   | Customer phone number (format `+55...`)     |
| `proposal_data.created_at` | ISO 8601 | Proposal creation date                      |
| `proposal_data.updated_at` | ISO 8601 | Last update date                            |
| `status_change.old_status` | string   | Previous status                             |
| `status_change.new_status` | string   | New status                                  |

## HTTP Headers

Each notification sent by Bull includes the following headers:

| Header          | Description                                    |
| --------------- | ---------------------------------------------- |
| `Content-Type`  | `application/json`                             |
| `X-Environment` | Origin environment (`staging` or `production`) |
| `X-Request-Id`  | Unique request identifier                      |

### Authentication (optional)

Webhook authentication is optional and configured by originator during onboarding. The available methods are:

| Method            | Sent Header                       | Description                            |
| ----------------- | --------------------------------- | -------------------------------------- |
| No authentication | —                                 | No authentication header is sent       |
| API Key           | Custom header (e.g.: `x-api-key`) | API key configured by the partner      |
| Bearer Token      | `Authorization: Bearer {token}`   | Bearer token configured by the partner |

{% hint style="info" %}
**Optional authentication**

Webhook authentication is optional. If you want to receive webhooks with authentication, inform the desired method and credentials to the Bull team during setup.
{% endhint %}

## Retry and Fault Tolerance

The webhook system has an automatic retry mechanism:

| Attempt | Delay       | Behavior                       |
| ------- | ----------- | ------------------------------ |
| 1st     | Immediate   | First delivery attempt         |
| 2nd     | \~1 second  | Automatic retry                |
| 3rd     | \~2 seconds | Retry with exponential backoff |
| 4th     | \~4 seconds | Last retry                     |
| Failure | —           | Message goes to the DLQ queue  |

### Endpoint Requirements

* Respond with **HTTP 200** within **30 seconds**
* Accept `Content-Type: application/json`
* Be available over **HTTPS**
* Implement **idempotency** (the same notification may arrive more than once)

{% hint style="success" %}
**Idempotency**

Use the `proposal_id` + `status_change.new_status` as an idempotency key to avoid duplicate processing.
{% endhint %}

## Setup

To configure webhook delivery:

1. **Implement** an HTTPS endpoint that accepts POST with JSON
2. **Provide** the endpoint URL to the Bull team
3. **(Optional)** Provide the desired authentication method (`apikey` or `bearer`) and the corresponding credentials
4. **Test** in the staging environment before going to production

## What happens after the customer signs via UNICO

When the customer accesses the `user_redirect_url` link and completes the process in UNICO, the system performs biometric security validations **before** marking the signature as valid. It is not enough for the process to have finished — all identity checks must be approved.

### Conditions for a Valid Signature

For the status to advance to `SIGNED`, **all** the 4 conditions below must be met:

| # | Check                 | Required Condition               | Description                                                 |
| - | --------------------- | -------------------------------- | ----------------------------------------------------------- |
| 1 | Process result        | `PROCESS_RESULT_OK`              | Process completed without technical errors                  |
| 2 | Authentication result | `AUTHENTICATION_RESULT_POSITIVE` | Identity confirmed by facial biometrics                     |
| 3 | Liveness result       | `LIVENESS_RESULT_LIVE`           | Real person detected (prevents the use of photos/deepfakes) |
| 4 | Anti-fraud check      | `!= TRUST_RESULT_YES`            | CPF is not in the database of known fraudsters              |

### When the Signature Fails (`SIGNATURE_FAILED`)

If any condition is not met, the proposal goes to `SIGNATURE_FAILED` and you will receive the corresponding webhook:

| Scenario              | Cause                                       | Resulting Status                                                |
| --------------------- | ------------------------------------------- | --------------------------------------------------------------- |
| Inconclusive identity | Biometrics could not confirm the customer   | `SIGNATURE_FAILED`                                              |
| Negative liveness     | System detected a printed photo or deepfake | `SIGNATURE_FAILED`                                              |
| Fraudster detected    | CPF found in the fraudsters database        | `SIGNATURE_FAILED`                                              |
| Process not completed | Customer abandoned or deadline expired      | Remains `AWAITING_SIGNATURE` until the deadline, then `EXPIRED` |

{% hint style="warning" %}
**SIGNATURE\_FAILED is a final status**

Once the proposal reaches `SIGNATURE_FAILED`, it cannot be reactivated. A new proposal must be started for the same CPF.
{% endhint %}

{% hint style="info" %}
**Most signatures are approved automatically**

In the vast majority of cases, the UNICO process completes successfully and the proposal advances directly from `AWAITING_SIGNATURE` to `SIGNED`. Biometric validations only block in cases of fraud or real technical failure.
{% endhint %}

## Relevant Statuses to Monitor

| Status                   | Meaning                                                               | Suggested Action                                                     |
| ------------------------ | --------------------------------------------------------------------- | -------------------------------------------------------------------- |
| `CREDIT_CHECK_COMPLETED` | Credit analysis completed                                             | Check offers via `GET /proposals/{id}`                               |
| `CONTRACT_GENERATED`     | Contract generated                                                    | Send `user_redirect_url` to the customer to sign                     |
| `AWAITING_SIGNATURE`     | Customer opened the UNICO link                                        | Wait for signature completion                                        |
| `SIGNED`                 | Biometric signature validated                                         | Wait for disbursement                                                |
| `SIGNATURE_FAILED`       | Signature rejected due to biometric failure                           | Inform the customer and start a new proposal                         |
| `PENDING_ANALYSIS`       | Internal fraud analysis (when applicable)                             | Bull internal process — wait                                         |
| `ANALYSIS_APPROVED`      | Fraud approved, disbursement imminent                                 | Wait for disbursement                                                |
| `ANALYSIS_REPROVED`      | Fraud rejected                                                        | Inform the customer — proposal will not be disbursed                 |
| `DISBURSED`              | Credit disbursed                                                      | Notify the customer, close the flow                                  |
| `DISBURSEMENT_FAILED`    | Disbursement failure                                                  | Stop the automated flow and trigger operational handling             |
| `CREDIT_ANALYSIS_FAILED` | Technical failure in credit analysis, including partner instabilities | Persist the received status and start the retry flow when applicable |
| `REJECTED`               | Proposal rejected in credit analysis                                  | Inform the customer                                                  |
| `EXPIRED`                | Contract expired without signature                                    | Restart the process if necessary                                     |
| `ERROR`                  | Dataprev endorsement error                                            | Wait for reprocessing (see maintenance window)                       |

## Credit Analysis Reprocessing

When credit analysis fails due to any technical instability, including Dataprev unavailability, credit engine failure, or an integrated partner error, the proposal may reach the webhook with status `CREDIT_ANALYSIS_FAILED`.

```json
{
  "event_type": "proposal.status.changed",
  "proposal_id": "c3874d5f-77cf-471d-91b6-6fab27abea7e",
  "status_change": {
    "old_status": "CREDIT_ANALYSIS_IN_PROGRESS",
    "new_status": "CREDIT_ANALYSIS_FAILED"
  }
}
```

### Partner Responsibility

1. Persist the status `CREDIT_ANALYSIS_FAILED` in the internal system.
2. Identify the proposal for a new processing attempt.
3. Call `POST /proposals/{proposalId}/credit-analysis/retry` using the same `proposalId`.
4. Continue tracking the proposal via webhook or `GET /proposals/{proposalId}`.
5. Inform the customer about the progress when this flow is part of your operation.

{% hint style="info" %}
**Reprocessing initiated by the partner**

Reprocessing is not triggered automatically by the webhook. The partner must store the received status and decide when to call the API retry route.
{% endhint %}

{% hint style="success" %}
**When to use retry**

Use retry for technical failures in credit analysis. Credit rejection (`REJECTED`) should not be handled with this route.
{% endhint %}

## Promotional Action After 20 Hours

This flow does not originate from a new webhook. It is handled by the partner from proposals that have already received an offer and have completed 20 hours without conversion.

### LP Version

When the partner chooses its own landing page, the customer's return must happen through the URL:

`https://{{URL_LP}}/simulation/retry?id={{proposal_id}}`

After that, the partner must:

1. bring the customer back to the landing page
2. collect new consent
3. call `POST /proposals/{proposal_id}/credit-analysis` with the same proposal

### API Version

When the partner operates directly via API, the flow follows the same 20-hour rule:

1. identify the proposal with the offer that did not convert
2. bring the customer back to the credit analysis flow
3. collect new consent
4. call `POST /proposals/{proposal_id}/credit-analysis` using the `proposal_id` of the proposal that already had an offer

If the proposal is eligible for the promotion, the new analysis may return a promotional offer.

{% hint style="info" %}
**Customer communication**

In the technical reprocessing and promotional action flows, communication with the customer is the partner's responsibility.
{% endhint %}

## Integration Example

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

app = Flask(__name__)

@app.route('/bull-webhook', methods=['POST'])
def handle_webhook():
    # 1. Process notification
    data = request.json
    proposal_id = data['proposal_id']
    new_status = data['status_change']['new_status']
    old_status = data['status_change']['old_status']

    print(f"Proposal {proposal_id}: {old_status} → {new_status}")

    # 2. Update internal system
    update_proposal_status(proposal_id, new_status)

    return jsonify({"received": True}), 200
```

```javascript
// Node.js with Express
app.post('/bull-webhook', (req, res) => {
  const { proposal_id, status_change } = req.body;

  console.log(`Proposal ${proposal_id}: ${status_change.old_status} → ${status_change.new_status}`);

  // Update internal system
  updateProposalStatus(proposal_id, status_change.new_status);

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


---

# 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/webhooks.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.
