Webhook Notifications

Use webhooks to get automatic, real-time updates on Mexican account validations. Register a callback URL with Bitso; when a validation reaches a terminal state, Bitso sends an HTTP POST to your endpoint.

📘

Important Guidelines

  • We don't guarantee the order of event delivery. Your application should process events that may arrive out of sequence.
  • The same event might be delivered multiple times. Your application must be idempotent.
  • Your endpoint must respond in under five seconds to avoid a timeout. Store the notification and process it asynchronously.
  • For transport, signing, and subscription details, see Webhooks.

Account Validation Event Type

Subscribe to this event type for Mexican account validations:

Event typeResourceWhen it fires
account_validationAccount validation (MX_SPEI)The validation reaches a terminal state (COMPLETED or FAILED).

There is one event type per resource, not per status. The lifecycle state is inside payload.status. The first webhook you receive for a given validation is the terminal result; create already returns PROCESSING.

PROCESSING regularly takes a few minutes. In a few cases it can take hours, depending on the receiving bank. If you need to respond quickly, handle that wait in your own flow and apply this webhook when it arrives. Compare verified_owner with the person you intend to pay.

How to Register Your Webhook

Subscribe with Create webhook subscriptions. For account validations, pass account_validation in events. See Managing Subscriptions. Fetch signing keys from List webhook public keys.

Webhook Payload Structure

Every webhook body has this envelope:

{
  "event": "account_validation",
  "payload": { }
}

payload is byte-identical to the response of Get account validation for the same id. Reuse the same parsing code for webhooks and GET responses. Nested type objects use lowercase keys (mx_spei) that match type.

What to do on each event

Branch first on event, then on payload.status.

payload.statusWhat to do
COMPLETEDRead payload.mx_spei.account.verified_owner and compare it with the person you intend to pay.
FAILEDRead payload.error.code. What each code means, and whether to create another validation, is in Failed validations. The code and message catalogue is in the API Reference.

Payload Examples

Examples below wrap the GET response shapes from the API Reference. One completed example is a proof of payment (CEP) that reported the identification as not available. That example still includes the owner name. A later validation returns that same identification.

{
  "event": "account_validation",
  "payload": {
    "id": "e7a12b34-f56c-7890-abcd-ef1234567890",
    "type": "MX_SPEI",
    "status": "COMPLETED",
    "mx_spei": {
      "account": {
        "type": "CLABE",
        "clabe": {
          "number": "810969370689174046"
        },
        "expected_owner": {
          "name": "Expected Owner Full Name"
        },
        "verified_owner": {
          "name": "Juan Pérez García",
          "identification_type": "CURP",
          "identification_number": "ABCD801231HDFABC01"
        }
      }
    },
    "created_at": "2026-06-25T14:00:00Z",
    "updated_at": "2026-06-25T14:32:00Z"
  }
}
{
  "event": "account_validation",
  "payload": {
    "id": "d1e2f3a4-b567-4890-abcd-ef1234567891",
    "type": "MX_SPEI",
    "status": "COMPLETED",
    "mx_spei": {
      "account": {
        "type": "CLABE",
        "clabe": {
          "number": "810969370689174046"
        },
        "expected_owner": {
          "name": "Expected Owner Full Name"
        },
        "verified_owner": {
          "name": "Ana Gómez Ruiz",
          "identification_type": "UNAVAILABLE",
          "identification_number": "ND"
        }
      }
    },
    "created_at": "2026-06-25T15:00:00Z",
    "updated_at": "2026-06-25T15:00:04Z"
  }
}
{
  "event": "account_validation",
  "payload": {
    "id": "bb11cc22-dd33-44ee-55ff-66aa77bb88cc",
    "type": "MX_SPEI",
    "status": "FAILED",
    "mx_spei": {
      "account": {
        "type": "CLABE",
        "clabe": {
          "number": "810969370689174046"
        },
        "expected_owner": {
          "name": "Expected Owner Full Name"
        }
      }
    },
    "error": {
      "code": "account_unreachable",
      "message": "The validation could not be completed because the account is unreachable."
    },
    "created_at": "2026-06-25T14:00:00Z",
    "updated_at": "2026-06-25T14:06:00Z"
  }
}
{
  "event": "account_validation",
  "payload": {
    "id": "cc22dd33-ee44-55ff-66aa-77bb88cc99dd",
    "type": "MX_SPEI",
    "status": "FAILED",
    "mx_spei": {
      "account": {
        "type": "CLABE",
        "clabe": {
          "number": "810969370689174046"
        },
        "expected_owner": {
          "name": "Expected Owner Full Name"
        }
      }
    },
    "error": {
      "code": "cep_empty",
      "message": "The validation could not be completed because the proof of payment (CEP) did not include a name or an identification."
    },
    "created_at": "2026-06-25T14:00:00Z",
    "updated_at": "2026-06-25T14:18:00Z"
  }
}
{
  "event": "account_validation",
  "payload": {
    "id": "dd33ee44-ff55-66aa-77bb-88cc99ddaaee",
    "type": "MX_SPEI",
    "status": "FAILED",
    "mx_spei": {
      "account": {
        "type": "CLABE",
        "clabe": {
          "number": "810969370689174046"
        },
        "expected_owner": {
          "name": "Expected Owner Full Name"
        }
      }
    },
    "error": {
      "code": "cep_mismatch",
      "message": "The validation could not be completed because the bank's proof of payment (CEP) reports a different account than the one validated."
    },
    "created_at": "2026-06-25T14:00:00Z",
    "updated_at": "2026-06-25T14:12:00Z"
  }
}

Polling as a fallback

Use Get account validation when you cannot rely on inbound HTTPS (local development, recovering from a webhook outage) or as a sanity check inside the webhook handler. The GET body matches payload.

List open lookups with List account validations (status=PROCESSING) for periodic reconciliation.

Idempotency at the consumer side

Delivery is at-least-once. The same (event, payload.id, payload.status, payload.updated_at) tuple may arrive more than once. Make your handler idempotent against the resource state, not the event delivery: persist a completed validation once, regardless of how many times the COMPLETED webhook arrives.



Did this page help you?