# Reserva de cupo --- - [Reserva de cupo](#reserva-de-cupo) - [Overview](#overview) - [Elige tu integración](#elige-tu-integracion) - [Estados de una reserva](#estados) - [1. Crear la reserva](#crear-la-reserva) - [Parámetros](#crear-parametros) - [Respuesta modalidad redirect](#crear-respuesta-redirect) - [Respuesta modalidad api](#crear-respuesta-api) - [2. Autorizar con tarjeta (solo modalidad api)](#autorizar) - [Parámetros de la tarjeta](#autorizar-parametros) - [Autenticación 3DS opcional](#autorizar-3ds) - [Ejemplo y respuesta](#autorizar-ejemplo) - [3. Cobro final](#cobro-final) - [4. Liberar la reserva](#liberar) - [Consultar y sincronizar](#consultar) - [Webhooks](#webhooks) - [Códigos de error](#codigos-de-error) - [Límites y notas](#limites) ## Overview [#overview] Una **reserva de cupo** retiene fondos en la tarjeta de tu cliente sin cobrarlos todavía. Después decides cuánto cobrar de verdad —hasta el valor reservado— o liberas la retención para que el cliente recupere su cupo. Sirve cuando **no conoces el valor final en el momento de la compra**: | Caso | Reservas | Cobras | |------|----------|--------| | Hotel | El valor de la estadía más un margen por consumos | Al hacer el check-out, con el consumo real | | Rent a car | El alquiler más el depósito de garantía | Al devolver el vehículo, descontando lo que aplique | | Delivery por peso o por consumo | Un estimado del pedido | Con el valor pesado o consumido | | Suscripción con periodo de prueba | El valor del plan | Cuando termina la prueba y el cliente sigue | El ciclo son tres momentos: ``` 1. Crear la reserva ─────► 2. El cliente autoriza ─────► 3a. Cobro final (retención activa) 3b. Liberar la reserva ``` :::note En la jerga de las redes de pago esto se llama **MIT** (*Merchant Initiated Transaction*) o *pre-autorización*. En esta documentación usamos «reserva de cupo», «cobro final» y «liberar la reserva». ::: **Una reserva es una transacción de tu comercio.** Aparece en tu reporte de transacciones desde que el cliente la autoriza, con el estado `Autorizada`. **No se abona a tu cuenta virtual mientras esté solo reservada**: el abono ocurre cuando aplicas el cobro final, y por el valor cobrado, no por el reservado. ## Elige tu integración [#elige-tu-integracion] Igual que en el resto de nuestra API, tienes dos modalidades. La eliges con el campo `checkout_type` al crear la reserva. | | `redirect` *(por defecto)* | `api` | |---|---|---| | **Quién captura la tarjeta** | Nosotros, en el checkout de Efipay | Tú, en tu propio checkout | | **Qué recibes al crear** | `checkout_url` | `token` | | **Peticiones para autorizar** | 1 (creas y rediriges) | 2 (creas y autorizas) | | **Requiere certificación PCI DSS** | No | **Sí** | | **3DS** | Lo gestionamos nosotros | Lo gestionas tú y nos envías el resultado | | **Llave de API** | Prueba o producción | **Solo producción** | **Flujo `redirect`:** ``` POST /v1/mit/pre-authorizations → { checkout_url, id } rediriges al cliente a checkout_url → el cliente ingresa su tarjeta webhook mit.pre_authorized → el cupo quedó reservado POST /v1/mit/pre-authorizations/{id}/confirm ó /void ``` **Flujo `api`:** ``` POST /v1/mit/pre-authorizations → { id, token } POST /v1/mit/pre-authorizations/{id}/authorize → el cupo quedó reservado POST /v1/mit/pre-authorizations/{id}/confirm ó /void ``` :::caution La modalidad `api` recibe el número de tarjeta y el CVV en tu servidor. Solo úsala si tu plataforma está certificada en PCI DSS. Si no lo está, usa `redirect`: es igual de completa y el dato sensible nunca pasa por tu sistema. ::: ## Estados de una reserva [#estados] | `status` | Etiqueta | Qué significa | |---|---|---| | `Iniciada` | Esperando al cliente | La reserva existe pero el cliente aún no autorizó | | `Pre-autorizada` | Cupo reservado | Hay fondos retenidos. Puedes cobrar o liberar | | `Confirmada` | Cobrada | El cobro final se aplicó. El dinero entra a tu cuenta virtual | | `Anulada` | Reserva liberada | El cliente recuperó su cupo. No hubo cobro | | `Vencida` | Reserva vencida | Pasó la vigencia sin cobro. El cupo se libera solo | | `Rechazada` | Rechazada | La red no aprobó la reserva | | `Fallida` | Fallida | No se pudo procesar | | `Indeterminada` | Verificando | No recibimos respuesta de la red. **No reintentes**: estamos verificando si el cupo quedó reservado | Puedes consultar este catálogo en `GET /api/v1/resources/mit/status-enum`. --- ## 1. Crear la reserva [#crear-la-reserva] `POST /api/v1/mit/pre-authorizations` ### Parámetros [#crear-parametros] | Nombre del campo | Descripción | Reglas | | - | - | - | | description | Qué se está reservando. El cliente lo ve en el checkout y tú en tu reporte | `['required', 'string', 'max:255']` | | estimated_amount | Valor a reservar. Es el techo del cobro final: no podrás cobrar más que esto | `['required', 'numeric', 'gt:0', 'max:99999999.99']` | | checkout_type | `redirect` (por defecto) o `api`. Ver [enumeraciones](/resources) | `['nullable', 'string', 'in:redirect,api']` | | tax | IVA incluido dentro de `estimated_amount`. Debe ser menor a `estimated_amount` | `['nullable', 'numeric', 'min:0']` | | references | Hasta 5 referencias para identificar la reserva en tus sistemas | `['nullable', 'array', 'max:5']` | | references.*.referenceKey | Nombre de la referencia. **Solo letras, números y espacios** | `['required_with:references', 'string', 'regex:/^[\pL\pN ]+$/u', 'max:64']` | | references.*.referenceDescription | Valor de la referencia. **Solo letras, números y espacios** | `['required_with:references', 'string', 'regex:/^[\pL\pN ]+$/u', 'max:22']` | | customer | Datos del cliente, para prellenar el checkout | `['nullable', 'array']` | | customer.name | Nombre del cliente | `['nullable', 'string', 'max:255']` | | customer.email | Correo del cliente | `['nullable', 'email', 'max:255']` | | webhook_url | URL a la que notificaremos los cambios de estado de **esta** reserva. Si no la envías usamos la de tus opciones avanzadas | `['nullable', 'url', 'max:255']` | | redirect_url | A dónde vuelve el cliente después del checkout (modalidad `redirect`) | `['nullable', 'url', 'max:255']` | | checkout_template_id | [ID de tu plantilla de checkout](/resources#checkout-templates) | `['nullable', 'integer', 'exists:checkout_templates,id']` | :::danger Este endpoint **no recibe datos de tarjeta**. Si envías `number`, `cvv`, `card_token` o similares la petición se rechaza con un 422 explicando por qué. En `redirect` los ingresa el cliente; en `api` van en el paso 2. ::: ### Respuesta modalidad redirect [#crear-respuesta-redirect] `POST /api/v1/mit/pre-authorizations` Cuerpo de ejemplo: ```json { "description": "Reserva habitación 402 - 3 noches", "estimated_amount": 850000, "checkout_type": "redirect", "references": [ { "referenceKey": "Reserva", "referenceDescription": "HAB402" } ], "customer": { "name": "Ana Gomez", "email": "ana@ejemplo.com" }, "webhook_url": "https://mi-hotel.com/webhooks/efipay" } ``` ```bash curl -X POST \ '/api/v1/mit/pre-authorizations' \ -H 'Authorization: Bearer ACCESS_TOKEN' \ -H "Content-type: application/json" \ -d '{ "description": "Reserva habitación 402 - 3 noches", "estimated_amount": 850000, "checkout_type": "redirect", "references": [ { "referenceKey": "Reserva", "referenceDescription": "HAB402" } ], "customer": { "name": "Ana Gomez", "email": "ana@ejemplo.com" }, "webhook_url": "https://mi-hotel.com/webhooks/efipay" }' ``` **Respuesta `201`:** ```json { "success": true, "pre_authorization": { "id": "019fba4a-5a7f-73df-b9a5-ec0ca585fd60", "status": "pendiente_autorizacion", "checkout_type": "redirect", "description": "Reserva habitación 402 - 3 noches", "estimated_amount": 850000, "currency": "COP", "references": [ { "referenceKey": "Reserva", "referenceDescription": "HAB402" } ], "expires_at": null, "checkout_url": "https://sag.efipay.co/Checkout/019fba4a-5a7f-73df-b9a5-ec0ca585fd60" } } ``` Redirige al cliente a `checkout_url`. Cuando autorice te llegará el webhook `mit.pre_authorized` con la reserva ya vigente. ### Respuesta modalidad api [#crear-respuesta-api] Idéntica, pero cambia `checkout_url` por `token`: ```json { "success": true, "pre_authorization": { "id": "019fba4a-5a7f-73df-b9a5-ec0ca585fd60", "status": "pendiente_autorizacion", "checkout_type": "api", "description": "Reserva habitación 402 - 3 noches", "estimated_amount": 850000, "currency": "COP", "references": [ { "referenceKey": "Reserva", "referenceDescription": "HAB402" } ], "expires_at": null, "token": "dASpVjbG0AJter1" } } ``` :::caution El `token` se devuelve **una sola vez**: en nuestra base solo queda su hash. Guárdalo junto al `id`, porque los dos juntos autentican el paso 2. Si lo pierdes, crea una reserva nueva. ::: --- ## 2. Autorizar con tarjeta (solo modalidad api) [#autorizar] `POST /api/v1/mit/pre-authorizations/{id}/authorize` Este es el paso que retiene los fondos. El par `id` + `token` del paso 1 autentica la operación: el `id` va en la URL y el `token` en el cuerpo. ### Parámetros de la tarjeta [#autorizar-parametros] | Nombre del campo | Descripción | Reglas | | - | - | - | | payment | Objeto con el token de la reserva | `['required', 'array']` | | payment.token | El `token` que te devolvió el paso 1 | `['required', 'string']` | | customer_payer | Datos de quien paga | `['required', 'array']` | | customer_payer.name | Nombre de quien paga | `['required', 'string', 'min:5', 'max:255']` | | customer_payer.email | Correo de quien paga. Solo caracteres alfanuméricos | `['required', 'email']` | | customer_payer.address_1 | Dirección principal | `['required', 'string', 'min:5', 'max:100']` | | customer_payer.address_2 | Dirección secundaria | `['nullable', 'string', 'min:1', 'max:100']` | | customer_payer.city | Ciudad | `['required', 'string', 'min:1', 'max:100']` | | customer_payer.state | Departamento o estado | `['required', 'string', 'min:1', 'max:100']` | | customer_payer.country | País en ISO3. Ver [lista de países](/resources) | `['required', 'string', 'in:COL,USA,MEX,...']` | | customer_payer.zip_code | Código postal | `['required', 'numeric', 'digits_between:1,10']` | | customer_payer.dialling_code | Indicativo telefónico, con `+` (por ejemplo `+57`) | `['required', 'regex:/^\+\d{1,3}$/i']` | | customer_payer.cellphone | Celular, solo dígitos | `['required', 'numeric', 'digits_between:5,15']` | | customer_payer.identification_type | Tipo de documento. Ver [enumeraciones](/resources) | `['nullable', 'in:CC,CE,TI,PPT,DNI,NIT,Pasaporte,Otro']` | | customer_payer.id_number | Número de documento. No puede ser un número de tarjeta | `['nullable', 'digits_between:5,15']` | | payment_card | Datos de la tarjeta | `['required', 'array']` | | payment_card.number | Número de la tarjeta, sin espacios | `['required', 'numeric', 'digits_between:14,16']` | | payment_card.name | Nombre impreso en la tarjeta. Solo letras y espacios | `['required', 'string']` | | payment_card.expiration_date | Vencimiento en formato `YYYY-MM`, con mes entre `01` y `12`. No puede estar vencida | `['required', 'date_format:Y-m', 'after_or_equal:']` | | payment_card.cvv | **Obligatorio.** Código de seguridad de 3 o 4 dígitos | `['required', 'regex:/^\d{3,4}$/i']` | | payment_card.installments | Número de cuotas | `['required', 'integer', 'between:1,60']` | | payment_card.redirect_url | A dónde volver si hay una autenticación intermedia | `['nullable', 'url', 'max:500']` | | browser_information | Datos del navegador del comprador | `['nullable', 'array']` | | browser_information.ipAddress | IP del comprador, para el antifraude | `['nullable', 'ipv4']` | :::danger El CVV es **obligatorio** en una reserva de cupo: la red la rechaza sin él. Por eso **no se aceptan tarjetas tokenizadas** en este endpoint —un token no incluye CVV—. Si envías `payment_card.token` recibirás un 422 explicándolo. El soporte de tarjetas tokenizadas para reservas está en evaluación con la red. ::: ### Autenticación 3DS opcional [#autorizar-3ds] Si autenticaste al tarjetahabiente con 3D Secure por tu cuenta, envíanos el resultado en `three_ds` y lo reenviamos a la red. **Cada franquicia usa campos distintos**; enviar los de la otra hace que la reserva sea rechazada. :::note En las reglas verás `nullable` en todos: Laravel los acepta ausentes, pero después validamos la combinación según la franquicia de la tarjeta. La columna «Descripción» dice cuándo cada uno pasa a ser obligatorio. ::: **Visa** — la franquicia se detecta por el BIN. Los tres campos son obligatorios si envías `three_ds`: | Nombre del campo | Descripción | Reglas | | - | - | - | | three_ds.eci | Electronic Commerce Indicator. Obligatorio si envías `three_ds` con una tarjeta Visa | `['nullable', 'string', 'size:2', 'in:05,06,07']` | | three_ds.cavv | Cardholder Authentication Verification Value. Obligatorio si envías `three_ds` con una tarjeta Visa | `['nullable', 'string', 'max:28']` | | three_ds.xid | Identificador de la transacción 3DS. Obligatorio si envías `three_ds` con una tarjeta Visa | `['nullable', 'string', 'max:28']` | **Mastercard** — los cuatro campos son obligatorios si envías `three_ds`: | Nombre del campo | Descripción | Reglas | | - | - | - | | three_ds.directory_server_transaction_id | Id de la transacción en el directorio, de exactamente 36 caracteres. Obligatorio si envías `three_ds` con una tarjeta que no sea Visa | `['nullable', 'string', 'size:36']` | | three_ds.ucaf_collection_indicator | Indicador UCAF. Obligatorio si envías `three_ds` con una tarjeta que no sea Visa | `['nullable', 'string', 'in:0,1,2,4,6,7']` | | three_ds.ucaf_authentication_data | Dato de autenticación UCAF. Obligatorio si envías `three_ds` con una tarjeta que no sea Visa | `['nullable', 'string', 'max:200']` | | three_ds.specification_version | Versión de la especificación, un solo carácter. Obligatorio si envías `three_ds` con una tarjeta que no sea Visa | `['nullable', 'string', 'max:1']` | Si no envías `three_ds`, la reserva se procesa sin autenticación 3DS. ### Ejemplo y respuesta [#autorizar-ejemplo] `POST /api/v1/mit/pre-authorizations/019fba4a-5a7f-73df-b9a5-ec0ca585fd60/authorize` Cuerpo de ejemplo: ```json { "payment": { "token": "dASpVjbG0AJter1" }, "customer_payer": { "name": "Ana Gomez Perez", "email": "ana@ejemplo.com", "address_1": "Calle 100 # 20-30", "city": "Bogota", "state": "Cundinamarca", "country": "COL", "zip_code": "110111", "dialling_code": "+57", "cellphone": "3001234567" }, "payment_card": { "number": "4916170011291313", "name": "Ana Gomez", "expiration_date": "2028-08", "cvv": "200", "installments": 1 } } ``` ```bash curl -X POST \ '/api/v1/mit/pre-authorizations/019fba4a-5a7f-73df-b9a5-ec0ca585fd60/authorize' \ -H 'Authorization: Bearer ACCESS_TOKEN' \ -H "Content-type: application/json" \ -d '{ "payment": { "token": "dASpVjbG0AJter1" }, "customer_payer": { "name": "Ana Gomez Perez", "email": "ana@ejemplo.com", "address_1": "Calle 100 # 20-30", "city": "Bogota", "state": "Cundinamarca", "country": "COL", "zip_code": "110111", "dialling_code": "+57", "cellphone": "3001234567" }, "payment_card": { "number": "4916170011291313", "name": "Ana Gomez", "expiration_date": "2028-08", "cvv": "200", "installments": 1 }, "three_ds": { "eci": "05", "cavv": "AAABCZIhcQAAAABZlyFxAAAAAAA=", "xid": "ODUzNTYzOTcwODU5NzQzMjE0NTY=" } }' ``` **Respuesta `201` — cupo reservado:** ```json { "success": true, "pre_authorization": { "id": "019fba50-8d0f-7362-a0b3-029215d91ebb", "operation": "pre_authorization", "status": "Pre-autorizada", "status_label": "Cupo reservado", "estimated_amount": 850000, "authorized_amount": 850000, "max_confirmable_amount": 850000, "currency": "COP", "installments": 1, "card": { "franchise": "visa", "bin": "491617", "last_four": "1313" }, "references": [ { "referenceKey": "Reserva", "referenceDescription": "HAB402" } ], "network_transaction_id": 320000303129, "authorization_code": "005077", "response_code": "00", "expires_at": "2026-08-07T10:15:00-05:00", "confirmable_until": "2026-08-07T10:15:00-05:00", "voidable_until": "2026-08-06T10:15:00-05:00", "can_confirm": true, "can_void": true, "blocked_reason": null, "checkout_url": null, "authorized_at": "2026-07-31T10:15:00-05:00", "confirmed_at": null, "voided_at": null, "created_at": "2026-07-31T10:15:00-05:00", "error": null } } ``` **Respuesta `422` — la red rechazó la reserva:** ```json { "success": false, "pre_authorization": { "id": "019fba50-8d0f-7362-a0b3-029215d91ebb", "status": "Rechazada", "status_label": "Rechazada", "response_code": "51", "can_confirm": false, "can_void": false, "error": { "code": "51", "message": "Fondos insuficientes. Pídele al cliente otra tarjeta.", "action": "Reintentar con otro medio de pago" } } } ``` :::note Usa `success` para decidir, no el código HTTP: siempre te devolvemos el estado completo de la reserva para que sepas exactamente en qué quedó. ::: --- ## 3. Cobro final [#cobro-final] `POST /api/v1/mit/pre-authorizations/{id}/confirm` Cobra el valor real. **Este es el momento en que el dinero se mueve** y en que la transacción pasa a `Aprobada` y se abona a tu cuenta virtual, por el valor cobrado. | Nombre del campo | Descripción | Reglas | | - | - | - | | amount | Valor real a cobrar. No puede superar `max_confirmable_amount` | `['required', 'numeric', 'gt:0', 'max:99999999.99']` | **Reglas de vigencia:** - Puedes cobrar **hasta la fecha de `confirmable_until`**, que es la vigencia de la reserva: **7 días para Visa** y **30 días para Mastercard** desde la autorización. - Puedes cobrar **menos** que lo reservado; la diferencia se libera. **No puedes cobrar más**. - Una reserva solo admite **un** cobro final. `POST /api/v1/mit/pre-authorizations/019fba50-8d0f-7362-a0b3-029215d91ebb/confirm` Cuerpo de ejemplo: ```json { "amount": 620000 } ``` ```bash curl -X POST \ '/api/v1/mit/pre-authorizations/019fba50-8d0f-7362-a0b3-029215d91ebb/confirm' \ -H 'Authorization: Bearer ACCESS_TOKEN' \ -H "Content-type: application/json" \ -d '{ "amount": 620000 }' ``` **Respuesta `200`:** ```json { "success": true, "operation": { "id": "019fba55-1c22-70aa-9f10-3d0b21ee7742", "operation": "confirmation", "status": "Confirmada", "status_label": "Cobrada", "estimated_amount": 620000, "response_code": "00", "authorization_code": "005081", "network_transaction_id": 320000303188 }, "pre_authorization": { "id": "019fba50-8d0f-7362-a0b3-029215d91ebb", "status": "Confirmada", "status_label": "Cobrada", "estimated_amount": 850000, "authorized_amount": 850000, "max_confirmable_amount": 620000, "can_confirm": false, "can_void": false, "confirmed_at": "2026-08-02T18:40:11-05:00" } } ``` Si la reserva ya no admite cobro, la respuesta llega con `success: false` y el motivo en `pre_authorization.blocked_reason`. --- ## 4. Liberar la reserva [#liberar] `POST /api/v1/mit/pre-authorizations/{id}/void` Suelta la retención para que el cliente recupere su cupo. No requiere cuerpo. **Reglas de vigencia:** - Puedes liberar hasta `voidable_until`, que es **24 horas antes** del vencimiento de la reserva. Esa ventana existe porque una liberación pedida sobre el filo del vencimiento puede cruzarse con la liberación automática de la red y quedar en un estado ambiguo. - Pasada esa ventana, **deja que la reserva venza**: el cupo se libera solo, sin cobro. Lo verás con estado `Vencida`. - Como nunca se abonó nada a tu cuenta virtual, liberar no genera ningún movimiento. Este endpoint **no recibe cuerpo**. `POST /api/v1/mit/pre-authorizations/019fba50-8d0f-7362-a0b3-029215d91ebb/void` ```bash curl -X POST \ '/api/v1/mit/pre-authorizations/019fba50-8d0f-7362-a0b3-029215d91ebb/void' \ -H 'Authorization: Bearer ACCESS_TOKEN' ``` **Respuesta `200`:** ```json { "success": true, "operation": { "operation": "void", "status": "Anulada", "status_label": "Reserva liberada", "response_code": "00" }, "pre_authorization": { "id": "019fba50-8d0f-7362-a0b3-029215d91ebb", "status": "Anulada", "status_label": "Reserva liberada", "can_confirm": false, "can_void": false, "voided_at": "2026-08-01T09:12:44-05:00" } } ``` --- ## Consultar y sincronizar [#consultar] ### Una reserva `GET /api/v1/mit/pre-authorizations/{id}` Devuelve la reserva completa, con los mismos campos que ves en la respuesta de crear y autorizar. No recibe parámetros. `GET /api/v1/mit/pre-authorizations/019fba50-8d0f-7362-a0b3-029215d91ebb` ```bash curl -X GET \ '/api/v1/mit/pre-authorizations/019fba50-8d0f-7362-a0b3-029215d91ebb' \ -H 'Authorization: Bearer ACCESS_TOKEN' \ -H 'Accept: application/json' ``` :::tip Respuesta satisfactoria ::: Código de respuesta: 200 ```json { "data": { "id": "019fba50-8d0f-7362-a0b3-029215d91ebb", "operation": "pre_authorization", "status": "Pre-autorizada", "status_label": "Cupo reservado", "estimated_amount": 850000, "authorized_amount": 850000, "max_confirmable_amount": 850000, "currency": "COP", "installments": 1, "card": { "franchise": "visa", "bin": "491617", "last_four": "1313" }, "references": [], "network_transaction_id": 320000303188, "authorization_code": "005081", "response_code": "00", "expires_at": "2026-08-08T18:40:11-05:00", "confirmable_until": "2026-08-08T18:40:11-05:00", "voidable_until": "2026-08-07T18:40:11-05:00", "can_confirm": true, "can_void": true, "blocked_reason": null, "checkout_url": null, "authorized_at": "2026-08-01T18:40:11-05:00", "confirmed_at": null, "voided_at": null, "created_at": "2026-08-01T18:39:02-05:00", "error": null } } ``` :::danger No encontramos la reserva ::: Código de respuesta: 404 ```json { "message": "No encontramos la reserva de cupo." } ``` ### Todas tus reservas `GET /api/v1/mit/pre-authorizations` Paginado, de la más reciente a la más antigua. Estos parámetros de consulta **no se validan en el servidor**: un valor inesperado no produce un `422`, simplemente no filtra. | Nombre del campo | Descripción | | - | - | | status | Filtra por estado exacto, por ejemplo `Pre-autorizada`. Ver [estados](#estados) | | pending_confirmation | `true` devuelve solo las que todavía admiten cobro final | | per_page | Cuántas por página. **Por defecto 25** | `GET /api/v1/mit/pre-authorizations` ```bash curl -X GET \ '/api/v1/mit/pre-authorizations?status=Pre-autorizada&pending_confirmation=true&per_page=50' \ -H 'Authorization: Bearer ACCESS_TOKEN' \ -H 'Accept: application/json' ``` :::tip Respuesta satisfactoria ::: Código de respuesta: 200 ```json { "data": [ { "id": "019fba50-8d0f-7362-a0b3-029215d91ebb", "operation": "pre_authorization", "status": "Pre-autorizada", "status_label": "Cupo reservado", "estimated_amount": 850000, "authorized_amount": 850000, "max_confirmable_amount": 850000, "currency": "COP", "can_confirm": true, "can_void": true, "confirmable_until": "2026-08-08T18:40:11-05:00", "created_at": "2026-08-01T18:39:02-05:00" } ], "links": { "first": "https://sag.efipay.co/api/v1/mit/pre-authorizations?page=1", "last": "https://sag.efipay.co/api/v1/mit/pre-authorizations?page=1", "prev": null, "next": null }, "meta": { "current_page": 1, "from": 1, "last_page": 1, "per_page": 50, "to": 1, "total": 1 } } ``` ### Sincronizar contra la red `POST /api/v1/mit/pre-authorizations/{id}/sync` Consulta el estado real en la red y actualiza la reserva. **No recibe cuerpo.** Úsalo cuando el estado sea `Indeterminada` —no recibimos respuesta y no sabemos si el cupo quedó reservado— y para obtener la fecha de vencimiento real, que la red solo entrega al consultar. `POST /api/v1/mit/pre-authorizations/019fba50-8d0f-7362-a0b3-029215d91ebb/sync` ```bash curl -X POST \ '/api/v1/mit/pre-authorizations/019fba50-8d0f-7362-a0b3-029215d91ebb/sync' \ -H 'Authorization: Bearer ACCESS_TOKEN' \ -H 'Accept: application/json' ``` :::caution Ante un estado `Indeterminada`, **no reintentes la autorización**: podrías reservar el cupo dos veces. Llama a `/sync` o espera nuestro webhook; nosotros reconciliamos automáticamente. ::: --- ## Webhooks [#webhooks] Te notificamos cada cambio de estado en la URL de `webhook_url` de la reserva o, si no la enviaste, en el `result_urls.webhook` de tus opciones avanzadas. | Evento | Cuándo llega | |---|---| | `mit.pre_authorized` | El cliente autorizó: hay fondos retenidos | | `mit.declined` | La red rechazó la reserva | | `mit.confirmed` | Se aplicó el cobro final | | `mit.voided` | Se liberó la reserva | | `mit.expiring_soon` | La reserva vence pronto y todavía no la cobraste | | `mit.expired` | La reserva venció y el cupo se liberó solo | **Cuerpo:** ```json { "event": "mit.pre_authorized", "pre_authorization": { "id": "019fba50-8d0f-7362-a0b3-029215d91ebb", "operation": "pre_authorization", "status": "Pre-autorizada", "status_label": "Cupo reservado", "estimated_amount": 850000, "authorized_amount": 850000, "max_confirmable_amount": 850000, "currency": "COP", "card": { "franchise": "visa", "bin": "491617", "last_four": "1313" }, "references": [ { "referenceKey": "Reserva", "referenceDescription": "HAB402" } ], "network_transaction_id": 320000303129, "authorization_code": "005077", "expires_at": "2026-08-07T10:15:00-05:00", "confirmable_until": "2026-08-07T10:15:00-05:00", "voidable_until": "2026-08-06T10:15:00-05:00", "can_confirm": true, "can_void": true, "error": null } } ``` :::note La ruta debe ser de tipo **post**. Enviamos un header **Signature** con la firma del cuerpo, para que verifiques que no fue manipulado. Se firma con el token de webhooks de tu comercio, que encuentras [aquí](https://sag.efipay.co/documentacion/api-key), usando HMAC-SHA256. Si tu aplicación no responde `2xx` reintentamos a los 10s y luego a los 100s. Después de eso no hay más intentos. ::: `mit.expiring_soon` es el que evita que se te pase un cobro: llega mientras la reserva todavía es cobrable, así que puedes cobrarla o dejarla vencer a conciencia. --- ## Códigos de error [#codigos-de-error] Puedes traer el catálogo completo, actualizado, desde `GET /api/v1/resources/mit/response-codes`. Cada entrada trae `code`, `message`, `action` y `retryable`, para que manejes los errores sin escribirlos a mano. **Errores propios de la reserva de cupo:** | Código | HTTP | Qué pasó y qué hacer | |---|---|---| | `MIT_PRODUCTION_KEY_REQUIRED` | 422 | Intentaste autorizar con una llave de prueba. Una reserva retiene fondos reales, así que la modalidad `api` solo funciona con llave de producción | | `MIT_AMOUNT_EXCEEDS_AUTHORIZED` | 422 | El cobro final supera lo reservado. Ajusta el monto a `max_confirmable_amount` | | `MIT_EXPIRED` | 422 | La reserva venció. Debes crear una reserva nueva | | `MIT_ALREADY_CONFIRMED` | 409 | Esta reserva ya tiene su cobro final aplicado | | `MIT_VOID_WINDOW_CLOSED` | 422 | Ya cerró la ventana para liberar (24 h antes del vencimiento). Deja que venza | | `MIT_FRANCHISE_NOT_ENABLED` | 422 | Esa franquicia no está habilitada para reservas en tu comercio. Escríbenos | | `MIT_INDETERMINATE` | 202 | No recibimos respuesta de la red. **No reintentes**; estamos verificando | | `MIT_IN_PROGRESS` | 429 | Ya hay una autorización en curso para esta reserva. Espera el resultado | **Códigos de la red más frecuentes** (llegan en `pre_authorization.error.code`): | Código | Qué pasó | |---|---| | `51` | Fondos insuficientes en el cupo del cliente | | `05` | Negada: la tarjeta puede estar bloqueada o el emisor no respondió | | `54` | Tarjeta vencida | | `M02` | La reserva no está en un estado que admita esta operación | | `M12` | El valor del cobro final no corresponde al reservado | | `309` | El tipo de transacción MIT no admite 3DS | | `310` | El ECI enviado no es válido: debe ser `05`, `06` o `07` | | `319` / `320` | Enviaste el objeto 3DS de la otra franquicia | --- ## Límites y notas [#limites] - **Moneda:** solo COP. - **Franquicias:** Visa y Mastercard. Amex, Diners y Codensa no admiten reserva de cupo. - **Vigencia:** 7 días para Visa, 30 días para Mastercard, contados desde la autorización. La fecha exacta viene en `expires_at`. - **Cuotas:** de 1 a 60. - **CVV obligatorio**, y por eso todavía no hay soporte de tarjetas tokenizadas para reservas. Está en evaluación con la red. - **Referencias:** solo letras, números y espacios; hasta 22 caracteres cada una. Procura que sean únicas por día: son la forma de identificar la reserva ante la red si hay que investigar una operación. - **Cuenta virtual:** una reserva no abona nada mientras esté solo reservada. El abono ocurre con el cobro final, por el valor cobrado. - **Reporte de transacciones:** la reserva aparece desde que el cliente la autoriza, con estado `Autorizada`, y pasa a `Aprobada` al cobrarla.