Webhooks

This comprehensive webhook integration guide helps developers implement real-time notifications for verification processes.

🔔

Webhook Notifications

We strongly recommend enabling webhook notifications. Buró de Ingresos' verification processes vary by data source and can take anywhere from 10 seconds to 8 minutes depending on your configuration.

When properly implemented, webhooks allow you to automatically trigger backend processes that rely on employment and income verification data.

Below, we provide use cases and examples to help you integrate webhook notifications successfully.

Introduction

Webhooks allow Buró de Ingresos to communicate with your application in real-time. All you need to do is provide us with a URL to which we can send notifications (HTTP POST requests) to keep you updated on the status of your verification requests. The details of each event are sent in the request body in JSON format, allowing you to automatically trigger processes in your backend.

Let's jump right into creating your first webhook!

Create Your First Webhook

To register a webhook you can use our our CREATE webhook endpoint referenced in the API docs. Here's an example:

curl --request POST \
     --url https://api.burodeingresos.com/webhooks \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --data '
{
  "description": "Production webhooks",
  "endpoint_url": "https://webhook.site/0d4af195-9868-4f72-b5d0-c1173190a012"
}
'

Successful Response:

{
  "id": "d1e2f3a4-b5c6-d7e8-f9a0-b1c2d3e4f5a6",
  "company_id": "a4b5c6d7-e8f9-a0b1-c2d3-e4f5a6b7c8d9",
  "description": "Production webhooks",
  "endpoint_url": "https://webhook.site/0d4af195-9868-4f72-b5d0-c1173190a012",
  "secret_key": "ZxyWvUt987654321SecretKeyExample0",
  "is_active": true,
  "created_at": "2025-07-10T19:25:45.123456Z",
  "updated_at": "2025-07-10T19:25:45.123456Z"
}

Data Schema of Webhook Events

We use a standardized data schema for webhook events:

{
  "event": "string",
  "verification_id": "string (UUID format)",
  "identifier": "string",
  "status": "string (enum: 'completed', etc.)",
  "data_available": "boolean",
  "can_retry": "boolean",
  "entities": "array<string>",
  "last_updated_at": "string (ISO 8601 datetime)",
  "timestamp": "string (ISO 8601 datetime)",
  "external_id": "string | null"
}

Webhook Notification Example

Let's say you integrated webhook notifications and one of your CURP verifications completed successfully with employment and invoice data. Here is an example of the request body your API could receive:

{
  "event": "verification.completed",
  "verification_id": "bd830637-ea6e-4888-80ab-a09f01fc9209",
  "identifier": "OICE940722HGFRST08",
  "status": "completed",
  "data_available": true,
  "can_retry": false,
  "entities": ["profile","employment","employment_files", "invoices","earnings"],
  "last_updated_at": "2025-04-29T10:37:25Z",
  "timestamp": "2025-04-29T10:37:25Z",
  "external_id":null
}

Webhook Events Catalog

The unified API triggers webhook events during the verification process:

EventDescription
verification.completedIndicates that a verification process has been completed across all configured data sources. This event is triggered regardless of whether data was found or not.
verification.historicalIndicates that existing information for the provided CURP was found immediately after submission. This event allows clients to retrieve available data right away, without waiting for the full verification process to complete.

Understanding Verification Status

The verification process can result in different outcomes:

  • in_progress: Verification is in progress.
  • completed: Verification was completed.

Retries

If we do not receive a 200 or 201 status code within 30 seconds after the first POST request to your webhook URL, we will make up to three additional attempts (stopping if we receive a 200 or 201), waiting approximately 30 seconds between each attempt.

Example Events


Verification in Progress with Historical Data

{
  "event": "verification.historical",
  "verification_id": "bd830637-ea6e-4888-80ab-a09f01fc9209",
  "identifier": "OICE940722HGFRST08",
  "status": "in_progress",
  "data_available": true,
  "can_retry": false,
  "entities": ["profile","employment","employment_files"],
  "last_updated_at": "2025-04-29T10:37:25Z",
  "timestamp": "2025-04-29T10:37:25Z",
  "external_id":null
}

Successful Verification with Full Data

{
  "event": "verification.completed",
  "verification_id": "bd830637-ea6e-4888-80ab-a09f01fc9209",
  "identifier": "OICE940722HGFRST08",
  "status": "completed",
  "data_available": true,
  "can_retry": false,
  "entities": ["profile","employment","employment_files", "invoices"],
  "last_updated_at": "2025-04-29T10:37:25Z",
  "timestamp": "2025-04-29T10:37:25Z",
  "external_id":null
}

Successful Verification with Partial Data

{
  "event": "verification.completed",
  "verification_id": "bd830637-ea6e-4888-80ab-a09f01fc9217",
  "identifier": "OICE940722HGFRST08",
  "status": "completed",
  "data_available": true,
  "can_retry": false,
  "entities": ["profile"],
  "last_updated_at": "2025-04-29T10:38:25Z",
  "timestamp": "2025-04-29T10:38:25Z"
}

Verification with No Data Found

{
  "event": "verification.completed",
  "verification_id": "bd830637-ea6e-4888-80ab-a09f01fc9211",
  "identifier": "OICE940722HGFRST08",
  "status": "completed",
  "data_available": false,
  "can_retry": false,
  "entities": [],
  "last_updated_at": "2025-04-29T10:32:15Z",
  "timestamp": "2025-04-29T10:32:15Z",
  "external_id":null
}

Verification Error

{
  "event": "verification.completed",
  "verification_id": "bd830637-ea6e-4888-80ab-a09f01fc9212",
  "identifier": "OICE940722HGFRST08",
  "status": "completed",
  "data_available": false,
  "can_retry": true,
  "entities": [],
  "last_updated_at": "2025-04-29T10:33:15Z",
  "timestamp": "2025-04-29T10:33:15Z",
  "external_id":null
}

Available Entities

When a verification is successful, the entities array will contain one or more of the following:

  • profile: Basic personal information, address and employment status
  • employment: Detailed employment records from IMSS/ISSSTE
  • employment_files: Employment documents from IMSS/ISSSTE
  • invoices: Invoice and payment history from payroll and invoicing systems

IP Whitelisting

For customers who restrict their systems to specific IP addresses, please note that our webhook outbound IPs may change periodically.

To ensure uninterrupted webhook delivery, we strongly recommend fetching the current IP list from this endpoint rather than hardcoding values in your system.

While IPs may change, the list returned by this endpoint is guaranteed to remain valid for at least 7 days.

👍

We recommend implementing an automated weekly refresh to keep your allow list up to date

curl --request GET \
     --url https://api.burodeingresos.com/outbound_ips \
     --header 'accept: application/json' \
     --header 'content-type: application/json'

Response

[
    {
      "ip_address": "string"
    }
]

🔑 Webhook Authentication

Buró de Ingresos supports multiple authentication methods for webhook delivery. These ensure secure communication between our system and your endpoints.

Authentication Types

When creating or updating a webhook, you can configure authentication to automatically include credentials with each request.


1. JWT Bearer Token

Generates and attaches a JWT token to each webhook request.

Configuration:

{
  "description": "Production webhook with JWT",
  "endpoint_url": "https://yourapi.com/webhook/jwt",
  "authentication": {
    "type": "jwt",
    "secret": "your-jwt-secret-key",
    "expiration_seconds": 3600
  }
}

How it works:

  • A JWT token is generated using your provided secret
  • The token expires based on expiration_seconds
  • Your endpoint should validate the JWT with the same secret

Example webhook request headers:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
X-Signature: 42FhFAPEEba//HkW3/8HSyB3j2S9hY2eTHAowk5PJcM=
Content-Type: application/json

2. Custom Headers

Sends predefined headers with every webhook request.

Configuration:

{
  "description": "Webhook with custom headers",
  "endpoint_url": "https://yourapi.com/webhook/headers",
  "authentication": {
    "type": "custom_headers",
    "headers": {
      "X-Api-Key": "your-api-key-here",
      "X-Client-Id": "your-client-id",
    }
  }
}

How it works:

  • The specified headers are included in each request
  • Your endpoint validates them for authentication
  • Useful for API key–based or custom authentication schemes

Example webhook request headers:

X-Api-Key: your-api-key-here
X-Client-Id: your-client-id
X-Custom-Auth: custom-value
X-Signature: 42FhFAPEEba//HkW3/8HSyB3j2S9hY2eTHAowk5PJcM=
Content-Type: application/json

3. Token Endpoint

Fetches a token from your authentication server before sending the webhook.

Configuration:

{
  "description": "Webhook with token endpoint",
  "endpoint_url": "https://yourapi.com/webhook/",
  "authentication": {
    "type": "token_endpoint",
    "url": "https://authserver.com/get-token",
    "method": "POST",
    "request_headers": {
      "Content-Type": "application/json",
      "Accept": "application/json"
    },
    "request_body": {
      "client_id": "your-client-id",
      "client_secret": "your-client-secret",
      "grant_type": "client_credentials"
    },
    "response_key": "access_token"
  }
}

How it works:

  • Before sending a webhook, our system requests a token from your endpoint
  • The response is parsed to extract the token using response_key
  • The token is included in the webhook request headers

Token endpoint request example:

POST https://authserver.com/get-token
Content-Type: application/json

{
  "client_id": "your-client-id",
  "client_secret": "your-client-secret",
  "grant_type": "client_credentials"
}

Expected token endpoint response:

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600
}

Example webhook request headers:

Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
X-Signature: 42FhFAPEEba//HkW3/8HSyB3j2S9hY2eTHAowk5PJcM=
Content-Type: application/json

Creating Webhooks with Authentication

Use the POST /webhooks endpoint to create a webhook with authentication:

curl --request POST \
     --url https://api.burodeingresos.com/webhooks \
     --header 'X-API-Key: your-api-key' \
     --header 'Content-Type: application/json' \
     --data '{
       "description": "Production webhook with JWT auth",
       "endpoint_url": "https://yourapi.com/webhook/handler",
       "authentication": {
         "type": "jwt",
         "secret": "your-jwt-secret",
         "expiration_seconds": 3600
       }
     }'

Updating Webhook Authentication

Use the PATCH /webhooks/{webhook_id} endpoint to update authentication settings:

curl --request PATCH \
     --url https://api.burodeingresos.com/webhooks/your-webhook-id \
     --header 'X-API-Key: your-api-key' \
     --header 'Content-Type: application/json' \
     --data '{
       "authentication": {
         "type": "custom_headers",
         "headers": {
           "X-Api-Key": "new-api-key",
           "X-Environment": "production"
         }
       }
     }'

🔐 Security: Verifying Webhooks

For security, every webhook event sent by the Buró de Ingresos includes a signature header that lets you verify the authenticity of the request. This ensures the event truly comes from us and that the payload hasn’t been altered in transit.

How it Works

Save this securely — it is used to verify all webhook events.

  • Each webhook notification includes an X-Signature (the signature header) and the raw request body (the JSON payload).

Verification steps

  1. Compute an HMAC using SHA-256 over the raw JSON body of the webhook request, with your secret_key.
  2. Compare the computed signature with the value in X-Signature.
  3. If they match, the webhook is valid. Otherwise, discard the request.

Example Verification (Node.js)

const crypto = require('crypto');

// Header from webhook request
const signatureHeader = '42FhFAPEEba//HkW3/8HSyB3j2S9hY2eTHAowk5PJcM=';

// Payload body from webhook request
const payload = {
    "event": "verification.completed",
    "verification_id": "019906e3-0a7a-71bc-a81c-14d2d8dbef9d",
    "identifier": "CERC660204HMCLCR09",
    "status": "completed",
    "data_available": true,
    "can_retry": false,
    "entities": ["profile","employment","employment_files"],
    "last_updated_at": "2025-09-01T20:06:39.976067400Z",
    "timestamp": "2025-09-01T20:06:39.976067400Z",
    "external_id":null

};

// Secret key from webhook creation response
const secretKey = 'p4AaudslHLBF9h5k7Brl0aGs3ijt0QM4yHhUWyQTp14';
const decodedSecret = Buffer.from(secretKey, 'base64url');

// Compute HMAC
const hmac = crypto.createHmac('sha256', decodedSecret);
hmac.update(JSON.stringify(payload), 'utf8');
const expectedSignature = hmac.digest('base64');

// Compare securely
const sigBuf = Buffer.from(signatureHeader, 'base64');
const expectedBuf = Buffer.from(expectedSignature, 'base64');

if (sigBuf.length !== expectedBuf.length) {
    console.log("Invalid signature header");
} else {
    const isValidSignature = crypto.timingSafeEqual(sigBuf, expectedBuf);
}

Troubleshooting

Common Issues

  1. Webhook not receiving events: Verify your endpoint URL is publicly accessible and returns 200/201
  2. Timeout errors: Ensure your endpoint responds within 30 seconds
  3. Missing data: Check the entities array to see what data is available

Migration from Legacy Webhooks

If you're migrating from the legacy user/account-based webhook system:

Key Changes

  1. Event Structure: Events now center around verification_id instead of account_id
  2. Identifier-Centric: Use CURP/RFC as the primary identifier
  3. Simplified Events: Single verification.completed event instead of multiple login events
  4. Entity-Based Data: The entities array tells you what data is available

What’s Next
  1. Set up your webhook endpoint
  2. Register it in the API
  3. Test with sample verification requests
  4. Implement proper error handling and retry logic
  5. Monitor webhook delivery