Outbound · Implementado por el POS
Webhook Spec
Esta página describe el contrato outbound: lo que tu POS recibe cuando BipBip entrega una orden nueva. Tu servidor expone este endpoint, BipBip lo llama.
Esta es la contraparte de la REST API
Transport contract
Contrato de transporte cerrado — estos parámetros son fijos y no se negocian por merchant.
| Parámetro | Valor |
|---|---|
| Path de creación | POST {baseUrl}/v1/order/{remoteId} |
| Path de actualizaciones | PUT {baseUrl}/v1/order/{remoteId}/{remoteOrderId}/events |
| Eventos en ese path | cancelled, driver_assigned, driver_released, delivered — discriminación por body.event |
| Versionado | En el path (/v1/) — futuras versiones pueden correr en paralelo |
| Body | JSON — schemas Order y OrderUpdateEvent |
| Timeout | 15 segundos por intento |
| ACK (creación) | HTTP 200 con { remoteOrderId } — obligatorio |
| ACK (status update) | HTTP 200 — body ignorado |
| Forward compat | Status desconocidos y campos top-level extra deben tolerarse → 200 OK e ignorar |
Headers firmados
BipBip firma cada request con HMAC-SHA256 sobre {timestamp}.{rawBody}usando el secret compartido durante onboarding. La guía de verificación HMAC documenta el algoritmo completo y los code samples.
| Header | Descripción |
|---|---|
| X-Bipbip-Signature-256 | HMAC-SHA256 en formato sha256=<hex> (lowercase) |
| X-Bipbip-Timestamp | Unix epoch segundos. Se rechaza si supera 300s (5 min) de skew. |
| X-Bipbip-Event-Type | order.created en el creation webhook y menu.change.completed.v1 en el de menú. El spec no lo declara en el endpoint de actualizaciones: ahí la discriminación es por body.event. |
| X-Bipbip-Schema-Version | Versión del schema del payload. Actualmente 1.0. BipBip solo la incrementa ante cambios incompatibles; agregar nuevos valores de status u otros campos es aditivo y no cambia la versión. |
| X-Bipbip-Delivery-Id | UUID único por dispatch. Usar para deduplicación. |
Estrategias de autenticación
Además de la firma HMAC —que siempre viaja—, cada endpoint de webhook puede configurar en el Merchant Portal una de tres estrategias para un header de autenticación adicional. La estrategia solo controla si se agrega ese header extra; nunca reemplaza la firma.
| Estrategia | Qué agrega |
|---|---|
| hmac_only default | Solo la firma HMAC-SHA256 (X-Bipbip-Signature-256 + X-Bipbip-Timestamp). No envía header Authorization. |
| static_headers | Agrega headers HTTP arbitrarios (ej. X-Api-Key, Authorization: Bearer <token> estático). Los valores sensibles se cifran con AWS KMS y se desencriptan al momento del dispatch. Soporta rotación dual-window de 24 h. |
| oauth_client_credentials | BipBip obtiene un Bearer token vía OAuth 2.0 Client Credentials e inyecta Authorization: Bearer <token>. El token se cachea (buffer de 60s antes de expirar); ante un 401 del comercio, BipBip invalida el cache y reintenta una vez con token fresco. Soporta client_secret_post y client_secret_basic, scopes y parámetros extra. Rotación dual-window de 24 h para el client_secret. |
La firma HMAC va SIEMPRE — sin importar la estrategia
Las tres estrategias incluyen la firma HMAC en X-Bipbip-Signature-256. La estrategia solo decide si se suma un header de auth adicional — no sustituye la firma.
Tu POS debe validar siempre la firma HMAC, cualquiera sea la estrategia configurada. No hay mTLS ni basic-auth propio.
Política de reintentos
BipBip implementa at-least-once delivery. Los reintentos se reencolan sin backoff exponencial — el siguiente intento llega en cuestión de segundos, no minutos.
- 5xx o error de red → BipBip reintenta (hasta
maxRetries, default 3 intentos totales, configurable por endpoint) - 4xx → terminal, BipBip no reintenta
- Timeout > 15s → trata el intento como fallo y reintenta
- HTTP 200 sin remoteOrderId (solo creation) → trata como fallo y reintenta
- Todos los reintentos llevan el mismo
X-Bipbip-Delivery-Id— la deduplicación se hace con ese UUID
Reintentos agotados del creation
order.created fallan, BipBip cancela la orden internamente (notifica al cliente) y la orden nunca llega al POS. Los PUT /events que fallan tras todos los intentos se descartan — BipBip no expone un evento de fallo separado.Endpoints
Tu POS expone estos dos endpoints. El segundo es un endpoint único de novedades — el tipo de evento se discrimina por body.event(cancelled, driver_assigned, driver_released, delivered).
event no es el estado de la orden
event dice qué pasó, no en qué estado quedó la orden. Solo cancelled y delivered tienen contraparte en el estado del recurso.
driver_assigned y driver_released pertenecen al ciclo del repartidor, que corre en paralelo al avance del comercio y no lo modifica. Una orden puede recibir un driver_assigned estando en preparing y quedarse en preparing.
Qué NO hacer: usar event para sobrescribir el estado interno de la orden salvo en esos dos casos. El estado del recurso se consulta con GET /api/v1/Orders/{orderKey} y nunca adopta un valor de repartidor.
/v1/order/{remoteId}Recibir orden nueva
BipBip envía este webhook cuando se crea una orden. El POS verifica la firma HMAC, deduplica con X-Bipbip-Delivery-Id, persiste la orden y responde HTTP 200 con { remoteOrderId } en el body. El valor de remoteOrderId es obligatorio — BipBip lo usa para componer la URL de las actualizaciones siguientes.
Path parameters
remoteIdstringrequiredHeaders (firmados por BipBip)
X-Bipbip-Signature-256stringrequired{timestamp}.{rawBody}, formato sha256=<hex>.X-Bipbip-TimestampintegerrequiredX-Bipbip-Event-Typestringrequiredorder.created en este endpoint.X-Bipbip-Delivery-IdUUIDrequiredBody
Returns — 200 OK (obligatorio)
remoteOrderIdstringrequiredPUT /events posteriores./v1/order/{remoteId}/{remoteOrderId}/eventsNovedades de la orden
Endpoint único para todas las novedades posteriores a la creación. body.event discrimina entre cancelled, driver_assigned, driver_released y delivered. event indica qué ocurrió, no el estado del recurso: solo cancelled y delivered lo modifican. driver_assigned puede recibirse más de una vez por orden (reasignación de conductor). El POS DEBE tolerar valores desconocidos y responder 200 OK ignorándolos. Solo se invoca si el creation webhook respondió con éxito.
Path parameters
remoteIdstringrequiredremoteOrderIdstringrequiredHeaders (firmados por BipBip)
X-Bipbip-Signature-256stringrequired{timestamp}.{rawBody}.X-Bipbip-TimestampintegerrequiredX-Bipbip-Schema-Versionstringrequired1.0. Versiones futuras lo incrementan y pueden traer campos top-level nuevos y valores nuevos de event.X-Bipbip-Delivery-IdUUIDrequiredEste endpoint no declara X-Bipbip-Event-Type
body.event y no hagas que tu handler dependa de la presencia de X-Bipbip-Event-Type acá.Body — discriminado por event
event. Cuatro variantes: cancelled (lleva reason; modifica el estado), driver_assigned (lleva driver; puede repetirse; no modifica el estado), driver_released (lleva releasedDriver, reason, expectedNext; informativo, no modifica el estado), delivered (modifica el estado).Returns — 200 OK
(body)anyCiclo de vida
Dos carriles en paralelo
El estado que reporta el comercio avanza lineal. La asignación del conductor no es un paso dentro de ese ciclo: corre en paralelo y no lo modifica. BipBip empieza a buscar conductor mientras la orden todavía está en preparing.
Estado de la orden (lo reporta el comercio)
Ciclo del repartidor (eventos, no estados)
Consecuencia práctica: driver_assigned puede llegar en cualquier momento antes de handed_over, incluso antes de que reportesready. No asumas un orden fijo entreready y driver_assigned.
driver_assigned puede llegar más de una vez
El operador BipBip puede reasignar el conductor antes de la entrega — y el sistema interno también puede rotar conductores automáticamente. Cada reasignación dispara un nuevo driver_assigned con los datos del conductor actualizado, mismo schema que el primero.
Qué hacer: mostrar siempre el conductor más reciente recibido. Qué NO hacer: idempotizar por orderKey + event y descartar el segundo evento — eso te deja mostrando un conductor que ya no es el correcto. Deduplicá por X-Bipbip-Delivery-Id.
El origen interno de la asignación (Auto / Operator / Driver hub) no viaja en el webhook — el payload es idéntico para los tres casos.
driver_released es informativo — NO hagas rollback de estado
Cuando llega driver_released, la orden lógicamente sigue en driver_assigned — no retrocede a ready ni a otro estado anterior.
Qué hacer: limpiar el conductor mostrado en la UI del POS y aguardar el próximo driver_assigned con el conductor nuevo. El campo expectedNext indica el próximo evento esperado (driver_reassignment).
delivered no te blinda contra un cancelled posterior
El evento cancelled puede llegar incluso después de delivered: un reembolso o una incidencia revierten una orden ya entregada.
Qué NO hacer: tratar la entrega como estado final que bloquee una cancelación posterior. De esa cancelación depende la conciliación del cobro.
Las actualizaciones están atadas al éxito del creation
order.created) nunca llegó al POS con HTTP 200, BipBip no envía ningún PUT /events posterior para esa orden.Forward compatibility — REQUERIDO
BipBip puede agregar valores nuevos a event y campos top-level extrasin incrementar la versión del schema. Tu POS debe:
- Ignorar elegantemente valores de
eventque no reconozca → responder200 OK. - No tratar campos top-level desconocidos como errores.
Webhook de cambios de menú
Un tercer endpoint, independiente del ciclo de la orden. BipBip lo llama cuando termina de aplicar un cambio de menú, y trae su propio event type: X-Bipbip-Event-Type: menu.change.completed.v1.
Notificación de resultado, sin efecto colateral
A diferencia de order.created —cuya falla total cancela la orden—, si se agotan los reintentos de este webhook, BipBip marca la entrega como fallida internamente pero no revierte ni cancela ningún cambio ni orden.
Los cambios ya aplicados permanecen aplicados; simplemente no habrás recibido la confirmación. Podés consultar el estado con GET /api/v1/menu/changes/{changeKey}.
Overrides por tienda de modifierOption: se persisten, pero hoy son no-op de cara al cliente
Cuando el cambio confirmado incluye price o name de un modifierOption a nivel de tienda, un resultado applied: true significa que BipBip persistió el override — pero la app de BipBip todavía no lo refleja al cliente final.
La REST API marca estos combos con pendingCustomerRollout: true en GET /api/v1/menu/capabilities.
Schema v1.0
Schemas
Definición de los cuerpos JSON que BipBip envía y del ACK esperado de vuelta. El headerX-Bipbip-Schema-Version: 1.0identifica esta versión. Los cambios aditivos (nuevos valores de status o campos opcionales) no la incrementan — los integradores existentes siguen funcionando si toleran valores desconocidos.
Cada schema declara los campos requeridos (required) y opcionales (optional). Los campos opcionales pueden ausentarse o adoptar el valor null según indique la descripción.
Order
Payload v1.0 que BipBip envía al POS al crearse una nueva orden (POST /v1/order/{remoteId}, X-Bipbip-Event-Type: order.created). Diseñado bajo el principio de minimización de datos: solo se incluyen los campos que el POS necesita para cocinar, facturar y cobrar la orden.
Identificación
orderKeystringrequiredord_ seguido de 16 caracteres base62. Debe utilizarse al invocar la REST API de BipBip para aceptar, rechazar o avanzar el estado de la orden.displayCodestringrequiredstoreRemoteId si se requiere unicidad global.storeRemoteIdstringrequiredcurrencystringrequiredHNL).createdAtISO-8601requiredexpiresAtISO-8601requiredFulfillment & cliente
Pago y totales
Líneas de la orden
Notas y facturación
customerNotestring | nulloptionalnull si no existe nota.Fulfillment
type discrimina la variante: delivery (el conductor de BipBip retira y entrega al cliente) o pickup (el cliente acude a la tienda). Cada variante popula uno solo de los dos timestamps de retiro.Fields
typeenumrequireddelivery: el conductor de BipBip retira la orden y la entrega. pickup: el cliente acude a la tienda.isExpressbooleanrequiredtrue si el cliente pagó delivery express → priorizar en cocina. Siempre false en pickup.prepareByISO-8601requireddriverPickupAtISO-8601 | nulloptionalnull en pickup.customerPickupAtISO-8601 | nulloptionalnull en delivery.Customer
firstName para permitir al cajero llamar al cliente al mostrador ("Orden para Ana"). No se incluyen apellido, teléfono ni correo. Solo presente en órdenes pickup; en delivery, customer = null.Fields
firstNamestringrequiredPayment
cash se cobra en handover; card y bips son pre-pagos en la app.Fields
amountToCollectdecimalrequiredmethods[].amount donde type=cash. 0.00 si está completamente pre-pagada.changeFordecimal | nulloptionalnull si está completamente pre-pagada.Summary
grandTotal = subtotal + taxes − discounts + additionalCharges.Fields
subtotaldecimalrequiredunitPrice × quantity de los items antes de impuestos. Constituye la línea de ingreso del comercio.taxesdecimalrequiredtax × quantity de los items. Constituye los impuestos a remitir.discountsdecimalrequireddiscounts[].amount (valor positivo).additionalChargesdecimalrequiredcharges[].amount.grandTotaldecimalrequiredpayment.methods[].amount.Charge
code es enum cerrado; códigos nuevos requieren bump mayor de schema con anuncio anticipado.Fields
codeenumrequireddelivery_fee, express_fee, service_fee, driver_tip, small_order_fee.amountdecimalrequiredbilledByenumrequiredbipbip (factura BipBip; no es ingreso del comercio) | merchant (ingreso del comercio).Discount
code: "GENERIC" cuando hay descuento; iteraciones futuras desglosarán cupones, lealtad, etc.Fields
codestringrequiredcode=GENERIC genérico. Iteraciones futuras desglosarán cupones, niveles de lealtad, etc.namestringrequiredamountdecimalrequiredfundedByenumrequiredbipbip (financia BipBip, comercio recibe precio completo) | merchant (financia el comercio, impacta su ingreso).Invoice
null en el caso predominante (consumidor final).Fields
taxIdstringrequiredbusinessNamestringrequiredItem
code) y el del POS del comercio (remoteCode) — este último cae al valor de code si no se configuró.Fields
codestringrequiredremoteCodestringrequiredremoteCode propio, este campo cae al valor de code (catálogo BipBip).namestringrequiredquantityinteger ≥ 1requiredunitPricedecimalrequiredtaxdecimalrequiredlineTotaldecimalrequired(unitPrice + tax) × quantity + modificadores. Se provee para conciliación rápida; el POS puede recalcularlo localmente.notestring | nulloptionalnull si no existe nota.ItemModifierGroup & ModifierOption
ItemModifierGroup
codestringrequirednamestringrequiredOrderAck
remoteOrderId es obligatorio: BipBip lo persiste y compone con él la URL de los status updates futuros. Respuestas sin este campo se consideran fallidas y se reintentan.Fields
remoteOrderIdstringrequiredintegration_order.remote_order_id y lo usa para componer la URL del webhook de novedades (PUT /v1/order/{remoteId}/{remoteOrderId}/events). Las respuestas sin este campo se reintentan.OrderUpdateEvent
event discrimina el evento: cancelled, driver_assigned, driver_released, delivered. Los campos específicos (reason, driver, releasedDriver, expectedNext) solo aparecen en el evento correspondiente.event describe qué ocurrió, no el estado del recurso
driver_assigned, driver_released) no modifican el estado de la orden. Solo cancelled y delivered tienen contraparte en él.Forward compatibility — REQUERIDO
200 OK e ignorar valores desconocidos de event, y tolerar campos top-level desconocidos.Fields
orderKeystringrequiredord_ + 16 chars base62). Puede usarse al invocar la REST API.remoteOrderIdstringrequiredOrderAck original. Se incluye por conveniencia; el path parameter es autoritativo.eventenumrequiredcancelled (lleva reason; modifica el estado), driver_assigned (lleva driver; puede repetirse por reasignación; no modifica el estado), driver_released (lleva releasedDriver, reason, expectedNext; informativo, sin rollback, no modifica el estado), delivered (modifica el estado). Pueden agregarse valores nuevos sin bump; ante un valor desconocido, responder 200 OK e ignorar.occurredAtISO-8601requiredreasonstring | nulloptionalevent=cancelled (valores: customer_requested, operator_action, acceptance_timeout, delivery_failed) o cuando event=driver_released (valor: manual_release). Pueden agregarse nuevos sin bump — tratar informativamente.expectedNextstring | nulloptionalevent=driver_released. Valor actual: driver_reassignment (BipBip está rotando al conductor; el siguiente driver_assigned traerá el nuevo). Pueden agregarse valores nuevos sin bump — tratar informativamente.OrderEventDriver
OrderUpdateEvent. Es el tipo de driver (cuando event = driver_assigned) y también de releasedDriver (cuando event = driver_released). Ausente en los demás eventos. Puede recibirse más de una vez por orden — cada vez con los datos del conductor actualmente asignado.Fields
fullNamestringrequiredphonestring | nulloptionalnull si el conductor no registró número o lo tiene marcado privado.MerchantErrorResponse
Fields (todos opcionales)
errorstringoptionalinvalid_payload, duplicate_delivery).messagestringoptionaltimestampISO-8601optional