# Penny Validation

Penny Validation verifies account ownership by sending a small validation
transaction to a destination instrument and retrieving CEP data from Banxico.

This guide explains when to use it and includes the API contract for starting
the validation.

## Entry points

Penny Validation can be started through the dedicated
[Create Penny Validation](#create-penny-validation) operation. Money Out can
also represent a Penny Validation when the transaction amount is `0.01` MXN and
the validation flow is enabled for the client.

The dedicated operation applies the Penny defaults for you: it sends `0.01` MXN,
uses MXN currency, rejects internal Monato-to-Monato transactions, and marks the
downstream transaction as a validation flow. When the validation CEP exists, the
response includes `metadata.dataCep`.

## Prerequisites

| Requirement | Reference |
|  --- | --- |
| Bearer token | [Authentication](/products/fincore/guides/authentication) |
| Source instrument from Centralizing Account | [Retrieve client accounts](/products/fincore/guides/accounts#retrieve-client-accounts) |
| Registered destination instrument | [Register instrument](/products/fincore/guides/instruments#register-instrument) |
| CEP webhook configuration | [Create webhook configuration](/products/fincore/guides/webhooks#create-webhook-configuration) |


## Flow

| Step | Action | OpenAPI contract | Save |
|  --- | --- | --- | --- |
| 1 | Create validation transaction | [Create Penny Validation](#create-penny-validation) | Transaction ID and tracking key. |
| 2 | Receive CEP result | [CEP webhook event](/products/fincore/guides/webhooks#cep-webhook-event) | Ownership validation result. |
| 3 | Handle delayed or failed CEP | [CEP Statuses](/products/fincore/guides/cep-statuses) | Final validation state. |


Treat the synchronous transaction response as pending. CEP lookup and webhook
delivery are asynchronous.

For field optionality and forward-compatible response parsing, see
[Core Concepts](/products/fincore/guides/concepts#field-presence-and-compatibility).

## Status handling

[CEP Statuses](/products/fincore/guides/cep-statuses) documents each status value, the transitions
between them, and the recommended handling. Use
the OpenAPI enum in [Create Penny Validation](#create-penny-validation) and
[CEP webhook event](/products/fincore/guides/webhooks#cep-webhook-event) as the source of truth for
supported values.

## Related pages

- [CEP Statuses](/products/fincore/guides/cep-statuses)
- [Retrieve a transaction](/products/fincore/guides/transactions#retrieve-a-transaction)
- [Webhooks](/products/fincore/guides/webhooks)
- [Instruments](/products/fincore/guides/instruments)
- [Error catalog](/products/fincore/guides/error-catalog)


## API contracts

### Create Penny Validation

Endpoint: `POST /v1/transactions/penny_validation`

#### Headers

```json
{
  "type": "object",
  "required": [
    "Authorization"
  ],
  "properties": {
    "Authorization": {
      "type": "string",
      "description": "Bearer token returned by the authentication flow.",
      "example": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
    },
    "Idempotency-Key": {
      "type": "string",
      "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-5[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$",
      "example": "66c0b04f-97d6-592d-8396-199819064afa",
      "description": "Optional deterministic UUID v5 used for safe retries. See [Idempotency](/products/fincore/guides/idempotency.md) for key generation, TTL, and conflict behavior.\n"
    }
  }
}
```

#### Request body

```json
{
  "$ref": "#/components/schemas/PennyValidationRequest",
  "components": {
    "schemas": {
      "PennyValidationRequest": {
        "type": "object",
        "description": "Request to start a Penny Validation transfer. This endpoint sets the validation flow explicitly and uses a `0.01` MXN transaction internally.\n",
        "required": [
          "client_id",
          "source_instrument_id",
          "destination_instrument_id"
        ],
        "properties": {
          "client_id": {
            "type": "string",
            "format": "uuid",
            "description": "Client UUID that owns the validation transaction.",
            "example": "c2d1d1e3-3340-4170-980e-e9269bbbc551"
          },
          "source_instrument_id": {
            "type": "string",
            "format": "uuid",
            "description": "Source instrument UUID used to send the validation amount.",
            "example": "709448c3-7cbf-454d-a87e-feb23801269a"
          },
          "destination_instrument_id": {
            "type": "string",
            "format": "uuid",
            "description": "Destination instrument UUID to validate.",
            "example": "d3fdb481-2058-46c8-807d-4eaf866ae1ec"
          },
          "description": {
            "type": "string",
            "maxLength": 40,
            "default": "Penny Validation",
            "description": "Optional concept for the validation transaction.",
            "example": "Account validation"
          },
          "external_reference": {
            "type": "string",
            "pattern": "^[0-9]{1,7}$",
            "maxLength": 7,
            "default": "0000001",
            "description": "Optional numeric reference with a maximum of 7 digits.",
            "example": "1234567"
          }
        }
      }
    }
  }
}
```

#### Request example

```json Payload application/json
{
  "client_id": "c2d1d1e3-3340-4170-980e-e9269bbbc551",
  "source_instrument_id": "709448c3-7cbf-454d-a87e-feb23801269a",
  "destination_instrument_id": "d3fdb481-2058-46c8-807d-4eaf866ae1ec",
  "description": "Account validation",
  "external_reference": "1234567"
}
```

#### 200 response body

```json
{
  "$ref": "#/components/schemas/PennyValidationResponse",
  "components": {
    "schemas": {
      "MoneyAmount": {
        "type": "string",
        "description": "Decimal amount as a string with exactly two decimal places.",
        "pattern": "^[0-9]+\\.[0-9]{2}$",
        "example": "5000.00"
      },
      "Currency": {
        "type": "string",
        "enum": [
          "MXN"
        ],
        "example": "MXN"
      },
      "TransactionCategory": {
        "type": "string",
        "enum": [
          "CREDIT_TRANS",
          "DEBIT_TRANS",
          "INTER_TRANS",
          "OTHER"
        ],
        "example": "DEBIT_TRANS"
      },
      "TransactionSubCategory": {
        "type": "string",
        "enum": [
          "OTHERS",
          "SPEI_CREDIT",
          "SPEI_DEBIT",
          "INT_DEBIT",
          "INT_CREDIT",
          "SPEI_REFUNDED",
          "SPEI_REFUNDED_CREDIT",
          "SPEI_REFUNDED_DEBIT",
          "INT_ADJ_CREDIT",
          "INT_ADJ_DEBIT"
        ],
        "example": "SPEI_DEBIT"
      },
      "TransactionStatus": {
        "type": "string",
        "enum": [
          "INITIALIZED",
          "IN_PROGRESS",
          "LIQUIDATED",
          "CANCELLED",
          "REFUNDED",
          "REJECTED",
          "DECLINED"
        ],
        "example": "INITIALIZED"
      },
      "InstrumentType": {
        "type": "string",
        "enum": [
          "RECEIVER",
          "SENDER_RECEIVER"
        ],
        "example": "RECEIVER"
      },
      "CardInstrumentDetail": {
        "type": "object",
        "description": "Debit-card instrument details returned by Fincore.",
        "properties": {
          "cardNumber": {
            "type": "string",
            "description": "Debit card number associated with the instrument.",
            "example": "5579072268574100"
          },
          "expirationDate": {
            "type": [
              "string",
              "null"
            ],
            "description": "Card expiration date when available; null otherwise.",
            "example": null
          },
          "holderName": {
            "type": "string",
            "description": "Debit-card holder name.",
            "example": "John Smith"
          }
        },
        "required": [
          "cardNumber",
          "holderName"
        ]
      },
      "ClabeInstrumentDetail": {
        "type": "object",
        "description": "CLABE instrument details returned by Fincore.",
        "properties": {
          "accountNumber": {
            "type": "string",
            "description": "Account number without bank prefix.",
            "example": "006487113111"
          },
          "clabeNumber": {
            "type": "string",
            "description": "Full 18-digit CLABE.",
            "example": "002118006487113111"
          },
          "holderName": {
            "type": "string",
            "description": "CLABE account holder name.",
            "example": "John Smith"
          }
        },
        "required": [
          "accountNumber",
          "clabeNumber",
          "holderName"
        ]
      },
      "TransactionInstrument": {
        "type": "object",
        "description": "Instrument snapshot associated with a transaction.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Instrument UUID.",
            "example": "709448c3-7cbf-454d-a87e-feb23801269a"
          },
          "bankId": {
            "type": "string",
            "format": "uuid",
            "description": "Bank UUID associated with the instrument.",
            "example": "9d84b03a-28d1-4898-a69c-38824239e2b1"
          },
          "clientId": {
            "type": "string",
            "format": "uuid",
            "description": "Client UUID associated with the instrument.",
            "example": "c2d1d1e3-3340-4170-980e-e9269bbbc551"
          },
          "ownerId": {
            "type": "string",
            "format": "uuid",
            "description": "UUID of the client or customer that owns the instrument.",
            "example": "c2d1d1e3-3340-4170-980e-e9269bbbc551"
          },
          "instrumentAlias": {
            "type": "string",
            "description": "Human-friendly label for the instrument.",
            "example": "Centralizing account"
          },
          "instrumentStatus": {
            "type": "string",
            "enum": [
              "ACTIVE",
              "BLOCKED",
              "DELETED"
            ],
            "description": "Current instrument lifecycle status.",
            "example": "ACTIVE"
          },
          "instrumentType": {
            "description": "Instrument usage type.",
            "$ref": "#/components/schemas/InstrumentType"
          },
          "instrumentDetail": {
            "description": "Details of the instrument as stored on the transaction. The shape depends on the instrument type: card destinations return `cardNumber`, `expirationDate` and `holderName`; CLABE instruments return `accountNumber`, `clabeNumber` and `holderName`.\n",
            "oneOf": [
              {
                "$ref": "#/components/schemas/CardInstrumentDetail"
              },
              {
                "$ref": "#/components/schemas/ClabeInstrumentDetail"
              }
            ]
          },
          "rfc": {
            "type": "string",
            "description": "RFC associated with the instrument holder.",
            "example": "XAXX010101000"
          },
          "customerId": {
            "type": "string",
            "format": "uuid",
            "description": "Customer UUID when the instrument belongs to a Business Unit.",
            "example": "bb1e8fde-e68e-48e9-a483-d32153c752c2"
          }
        }
      },
      "TransactionDataCep": {
        "type": "object",
        "description": "CEP validation metadata associated with a transaction.",
        "properties": {
          "cepUrl": {
            "type": "string",
            "format": "uri",
            "description": "Banxico CEP URL when the CEP document is available.",
            "example": "https://www.banxico.org.mx/cep/..."
          },
          "validationId": {
            "type": "string",
            "format": "uuid",
            "description": "Internal UUID for the CEP validation process.",
            "example": "f4ebe9af-50ac-42e5-97c7-3164d2693d6e"
          },
          "beneficiaryName": {
            "type": "string",
            "description": "Beneficiary name returned by the CEP validation process.",
            "example": "John Smith"
          },
          "beneficiaryRfc": {
            "type": "string",
            "description": "Beneficiary RFC returned by the CEP validation process.",
            "example": "XAXX010101000"
          },
          "status": {
            "type": "string",
            "enum": [
              "INITIALIZED",
              "PENDING",
              "DELAYED",
              "COMPLETED",
              "FAILED"
            ],
            "description": "Current CEP validation status.",
            "example": "PENDING"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "Timestamp when the CEP validation record was created.",
            "example": "2025-08-15T22:42:39.327Z"
          },
          "processedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Timestamp when CEP processing finished, or null while pending.",
            "example": null
          }
        }
      },
      "TransactionMetadata": {
        "type": "object",
        "description": "Optional transaction metadata returned for CEP validations and returns.",
        "properties": {
          "dataCep": {
            "description": "CEP validation metadata when available.",
            "$ref": "#/components/schemas/TransactionDataCep"
          },
          "dataReturn": {
            "type": "object",
            "description": "Return or refund metadata when available.",
            "properties": {
              "trackingId": {
                "type": "string",
                "example": "20250510FINCHFL2SFGP9KT"
              },
              "originalTrackingId": {
                "type": "string",
                "example": "20250509FINCHARNJK5NHQG"
              },
              "reason": {
                "type": "string",
                "example": "CANCELLED_ACCOUNT"
              },
              "reasonDescription": {
                "type": "string",
                "example": "Cuenta cancelada"
              }
            }
          }
        }
      },
      "MoneyOutResponse": {
        "type": "object",
        "description": "Money Out transaction accepted by Fincore.",
        "required": [
          "id",
          "bankId",
          "clientId",
          "externalReference",
          "trackingId",
          "description",
          "amount",
          "currency",
          "category",
          "subCategory",
          "transactionStatus"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Transaction UUID.",
            "example": "16811ee8-1ef9-4dd4-8d84-9c2df89cf302"
          },
          "bankId": {
            "type": "string",
            "format": "uuid",
            "description": "Bank UUID used by the source account.",
            "example": "9d84b03a-28d1-4898-a69c-38824239e2b1"
          },
          "clientId": {
            "type": "string",
            "format": "uuid",
            "description": "Client UUID that owns the transaction.",
            "example": "c2d1d1e3-3340-4170-980e-e9269bbbc551"
          },
          "externalReference": {
            "type": "string",
            "description": "Client-provided numeric reference.",
            "example": "1234567"
          },
          "trackingId": {
            "type": "string",
            "description": "Tracking key assigned to the transaction for reconciliation.",
            "example": "20250306FINCHVLIKQ5SKUM"
          },
          "description": {
            "type": "string",
            "description": "Payment concept sent with the transaction.",
            "example": "Supplier payment"
          },
          "amount": {
            "description": "Transaction amount as a decimal string with two decimals.",
            "$ref": "#/components/schemas/MoneyAmount"
          },
          "currency": {
            "description": "Transaction currency.",
            "$ref": "#/components/schemas/Currency"
          },
          "category": {
            "description": "Transaction category.",
            "$ref": "#/components/schemas/TransactionCategory"
          },
          "subCategory": {
            "allOf": [
              {
                "$ref": "#/components/schemas/TransactionSubCategory"
              }
            ],
            "description": "Transaction sub-type based on the destination:\n  - SPEI_DEBIT – external transfer to a non-Finco Pay bank account.\n  - INT_DEBIT – internal transfer routed to a Finco Pay account.\n"
          },
          "transactionStatus": {
            "description": "Current transaction status.",
            "$ref": "#/components/schemas/TransactionStatus"
          },
          "audit": {
            "type": "object",
            "description": "Transaction lifecycle timestamps.",
            "properties": {
              "createdAt": {
                "type": "string",
                "format": "date-time",
                "description": "Timestamp when the transaction was created.",
                "example": "2025-03-06 11:57:55.408000-06:00"
              },
              "updatedAt": {
                "type": "string",
                "format": "date-time",
                "description": "Timestamp when the transaction was last updated.",
                "example": "2025-03-06 11:57:55.408000-06:00"
              },
              "deletedAt": {
                "type": [
                  "null",
                  "string"
                ],
                "description": "Timestamp when the transaction was deleted, or null.",
                "example": null
              },
              "blockedAt": {
                "type": [
                  "null",
                  "string"
                ],
                "description": "Timestamp when the transaction was blocked, or null.",
                "example": null
              }
            }
          },
          "sourceInstrument": {
            "description": "Source instrument used to fund the transaction.",
            "$ref": "#/components/schemas/TransactionInstrument"
          },
          "destinationInstrument": {
            "description": "Destination instrument that receives the transaction.",
            "$ref": "#/components/schemas/TransactionInstrument"
          },
          "originalTransactionId": {
            "type": "string",
            "format": "uuid",
            "description": "Present on refund-related transactions.",
            "example": "a1392ef1-23f5-4e15-90cd-5d3d8d24d839"
          },
          "refundTransactionId": {
            "type": "string",
            "format": "uuid",
            "description": "Present on original transactions after refund.",
            "example": "957459ce-d4e3-40b5-b759-373e844ba1e8"
          },
          "metadata": {
            "description": "Optional additional transaction metadata, such as CEP or return details when available. Penny Validation responses use the `PennyValidationResponse` schema because `metadata.dataCep` is required for that flow.\n",
            "$ref": "#/components/schemas/TransactionMetadata"
          },
          "clientReference": {
            "type": "string",
            "description": "Optional client reference returned when it was supplied in the request.",
            "example": "INV-4567"
          }
        }
      },
      "PennyValidationMetadata": {
        "type": "object",
        "description": "Metadata required in Penny Validation responses.",
        "required": [
          "dataCep"
        ],
        "properties": {
          "dataCep": {
            "description": "CEP validation metadata produced by the Penny Validation flow.",
            "$ref": "#/components/schemas/TransactionDataCep"
          }
        }
      },
      "PennyValidationResponse": {
        "description": "Penny Validation transaction response. It uses the shared Money Out response shape and requires CEP metadata for this validation flow.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/MoneyOutResponse"
          },
          {
            "type": "object",
            "required": [
              "metadata"
            ],
            "properties": {
              "metadata": {
                "description": "Required CEP validation metadata for Penny Validation.",
                "$ref": "#/components/schemas/PennyValidationMetadata"
              }
            }
          }
        ]
      }
    }
  }
}
```

#### Response example

```json 200 application/json
{
  "id": "1eb4b5ac-09ac-4a64-b853-6939728621d2",
  "trackingId": "20250815FINCHPV123456",
  "transactionStatus": "INITIALIZED",
  "amount": "0.01",
  "currency": "MXN",
  "bankId": "9d84b03a-28d1-4898-a69c-38824239e2b1",
  "clientId": "c2d1d1e3-3340-4170-980e-e9269bbbc551",
  "externalReference": "1234567",
  "description": "Account validation",
  "category": "DEBIT_TRANS",
  "subCategory": "SPEI_DEBIT",
  "metadata": {
    "dataCep": {
      "status": "PENDING",
      "cepUrl": "https://www.banxico.org.mx/cep/...",
      "validationId": "f4ebe9af-50ac-42e5-97c7-3164d2693d6e"
    }
  }
}
```

```json 400 application/json
{
  "code": 9,
  "message": "API Error",
  "details": [
    null
  ]
}
```

```json 401 application/json
{
  "code": 16,
  "message": "API Error",
  "details": [
    {
      "reason": "UNAUTHORIZED",
      "domain": "CORE",
      "metadata": {
        "error_detail": "Invalid Credentials",
        "http_code": "401"
      }
    }
  ]
}
```

```json 404 application/json
{
  "code": 9,
  "message": "API Error",
  "details": [
    null
  ]
}
```

```json 409 application/json
{
  "code": 9,
  "message": "API Error",
  "details": [
    null
  ]
}
```

```json 500 application/json
{
  "code": 9,
  "message": "API Error",
  "details": [
    null
  ]
}
```

| Error | When it happens |
|  --- | --- |
| `400` | Validation request fields are missing, malformed, or invalid. |
| `401` | The bearer token is missing, expired, invalid, or not valid for the environment. |
| `404` | Source instrument, destination instrument, client, bank, or related account was not found. |
| `409` | The idempotency key conflicts with a previous request. |
| `500` | Unexpected server error. |