Skip to content

Bmonkey · Contrato de integración

This content is not available in your language yet.

Este contrato describe cómo tu sistema habla con Bmonkey — la llave maestra (mon-key) del ecosistema. Incluye operaciones, costos en Açaí, payloads JSON, snippets en cURL/TypeScript/Go y los eventos que recibirás por webhook.

OperaciónEndpointPlanoCosto
Crear plantilla de formularioPOST /v1/form-templatesControl0 Açaí
Crear submission KYCPOST /v1/kyc/submissionsData80 Açaí (con biometría) / 30 Açaí (sin)
Verificar identidad existentePOST /v1/subjects/{id}/reverifyData50 Açaí
Consultar subjectGET /v1/subjects/{id}Data1 Açaí
Listar submissionsGET /v1/submissionsData1 Açaí

Autenticación (OIDC — modelo MAU + extras)

Sección titulada «Autenticación (OIDC — modelo MAU + extras)»

El cobro de auth sigue el estándar Auth0 / Cognito: una cuota fija por usuario activo en el mes (MAU) que cubre logins ilimitados con sesión válida, más extras por las operaciones que consumen infraestructura real (envío de SMS, biometría, etc.).

OperaciónEndpointCostoProveedor
MAU — primer login del mes por usuario (cualquier método)POST /v1/auth/login4 AçaíBmonkey IdP
Logins adicionales del mismo MAU en el mismo mes0 Açaí
MFA SMS OTP enviadoPOST /v1/auth/mfa/sms6 AçaíAWS SNS
MFA Email OTP enviadoPOST /v1/auth/mfa/email1 AçaíAWS SES
MFA TOTP / Push verificadoPOST /v1/auth/mfa/totp o /push0 AçaíFCM / APNs
Step-up biométrico (eleva sesión a LoA3)POST /v1/auth/biometric5 AçaíAWS Rekognition
Inspeccionar / cerrar sesiónGET / DELETE /v1/auth/sessions/{id}1 Açaí
Emisión libre de token OIDC (servicio M2M)POST /v1/oauth/token1 Açaí

Plano del comercio (X-API-Key):

OperaciónEndpointCosto
Discover si existe wallet activo para un documentoPOST /v1/wallet/discover1 Açaí
Solicitar presentación (dispara consent del usuario)POST /v1/wallet/request-presentation5 Açaí
Recibir presentation aprobada (webhook → reuso completo)webhook bmonkey.presentation.granted32 Açaí (vs 80 KYC desde cero)

Plano del usuario (Bearer wallet session JWT):

OperaciónEndpointCosto
Listar grants activos del wallet del usuarioGET /v1/wallet/me/grants0 Açaí
Revocar un grantDELETE /v1/wallet/me/grants/{id}0 Açaí
Ver balance Açaí del usuarioGET /v1/wallet/me/balance0 Açaí
Ver historial de movimientosGET /v1/wallet/me/ledger0 Açaí
Listar passkeys enroladasGET /v1/wallet/me/passkeys0 Açaí
Revocar una passkeyDELETE /v1/wallet/me/passkeys/{id}0 Açaí
Iniciar registro WebAuthnPOST /v1/wallet/webauthn/register/begin0 Açaí
Confirmar registro WebAuthnPOST /v1/wallet/webauthn/register/finish0 Açaí

Plano público (consent token + WebAuthn):

OperaciónEndpointCosto
Ver detalles del consentGET /consent/{token}0 Açaí
Aprobar consent (emite presentation + crédita 5 Açaí al usuario)POST /consent/{token}/approve(cobrado al solicitante = 32)
Rechazar consentPOST /consent/{token}/reject0 Açaí
Iniciar login con passkeyPOST /v1/wallet/webauthn/login/begin0 Açaí
Finalizar login con passkeyPOST /v1/wallet/webauthn/login/finish0 Açaí
  1. Crear plantilla desde el admin o por API (POST /v1/form-templates).
  2. Tu app llama POST /v1/kyc/submissions con las respuestas del formulario y la selfie + documento en base64 o URLs presignadas.
  3. Bmonkey responde 202 con submission_id y un subject_id provisional.
  4. Worker procesa biometría + validación documental en background.
  5. Recibes webhook bmonkey.subject.verified (o .rejected) firmado con HMAC.
  6. Verificas la firma y guardas el resultado en tu sistema.
POST /v1/kyc/submissions HTTP/1.1
Host: api.digital-jungle.bjungle.com
X-API-Key: <api_key>
Content-Type: application/json
Idempotency-Key: 5f8d1c3e-1234-... # recomendado
{
"template_code": "persona-natural-v1",
"external_ref": "client-90234",
"answers": {
"tipo_documento": "CC",
"numero_documento": "1023456789",
"primer_nombre": "Camila",
"primer_apellido": "Rodríguez",
"fecha_nacimiento": "1992-04-18",
"direccion": "Calle 100 #15-22",
"ciudad": "Bogotá"
},
"biometric": {
"selfie_url": "https://uploads.tu-app.com/abc.jpg",
"document_url": "https://uploads.tu-app.com/cc.jpg"
}
}
{
"submission_id": "01J8FXY2K7HZWQ7M9P0G5R3T4N",
"subject_id": "0c4b5b32-3a89-4a45-9c2c-1a8c47e91234",
"status": "received",
"acai_debited": 80
}

Webhook resultado — bmonkey.subject.verified

Sección titulada «Webhook resultado — bmonkey.subject.verified»
{
"event_id": "5f8d1c3e-3b00-4d6e-8a9d-5e8c8b5f1234",
"tenant_id": "8a7b6c5d-1234-5678-9abc-def012345678",
"module": "bmonkey",
"event_type": "bmonkey.subject.verified",
"occurred_at": "2026-05-25T14:00:00Z",
"payload": {
"subject_id": "0c4b5b32-3a89-4a45-9c2c-1a8c47e91234",
"submission_id": "01J8FXY2K7HZWQ7M9P0G5R3T4N",
"external_ref": "client-90234",
"decision": "verified",
"biometric_score": 0.97,
"liveness_score": 0.99,
"document_match": true,
"attributes": {
"full_name": "Camila Rodríguez",
"document_id": "1023456789",
"document_type": "CC"
}
}
}
Ventana de terminal
curl -X POST https://api.digital-jungle.bjungle.com/v1/kyc/submissions \
-H "X-API-Key: $BMONKEY_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d @submission.json

Cada webhook llega con header X-Bjungle-Signature: sha256=<hex>. La firma es el HMAC-SHA256 del body crudo con el webhook_secret del tenant.

import { createHmac, timingSafeEqual } from 'node:crypto';
export function verify(rawBody: string, signatureHeader: string, secret: string): boolean {
const expected = createHmac('sha256', secret).update(rawBody).digest('hex');
const provided = signatureHeader.replace(/^sha256=/, '');
return timingSafeEqual(Buffer.from(expected), Buffer.from(provided));
}

3. Autenticación con niveles de aseguramiento

Sección titulada «3. Autenticación con niveles de aseguramiento»

Bmonkey emite tokens OIDC estándar con el claim acr (Authentication Context Class Reference) indicando el LoA alcanzado en la sesión. Tu app valida acr antes de autorizar acciones sensibles y hace step-up si falta nivel.

POST /v1/auth/login HTTP/1.1
Host: api.digital-jungle.bjungle.com
X-API-Key: <api_key>
Content-Type: application/json
{
"subject_ref": "client-90234",
"credentials": { "username": "camila@correo.co", "password": "***" },
"required_loa": "LoA2",
"redirect_uri": "https://app.cashpaya.co/oauth/callback"
}

Si las credenciales son válidas pero el LoA exigido pide MFA, la respuesta no es un token sino un challenge:

{
"challenge_id": "ch_01J8H1...",
"next_step": "mfa",
"available_factors": ["sms", "totp", "push"],
"expires_at": "2026-05-25T14:05:00Z",
"acai_debited": 1
}
POST /v1/auth/mfa/challenge HTTP/1.1
{ "challenge_id": "ch_01J8H1...", "factor": "totp", "code": "284913" }

Response — token OIDC final:

{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"id_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"acr": "LoA2",
"amr": ["pwd", "totp"],
"session_id": "ses_01J8H2..."
}

El claim acr viaja firmado dentro del JWT. Tu backend lo lee y exige step-up cuando un endpoint requiere LoA3:

if (decodedJwt.acr !== 'LoA3' && action === 'transfer') {
return redirectStepUp(decodedJwt.session_id, 'LoA3');
}

Permite que un tenant nuevo reuse una verificación que otro tenant ya hizo, con consentimiento del usuario y pagando 60 % menos. Ver el concepto completo en Wallet de identidad.

POST /v1/wallet/discover HTTP/1.1
{ "document_type": "CC", "document_id": "1023456789" }

Response (no expone claims, solo metadatos):

{
"wallet_exists": true,
"active_loa": "LoA3",
"expires_at": "2027-03-12T15:00:00Z",
"origin_tenant_known": true
}
POST /v1/wallet/request-presentation HTTP/1.1
{
"document_type": "CC",
"document_id": "1023456789",
"required_claims": ["full_name", "document", "address"],
"required_loa": "LoA3",
"purpose": "Onboarding en Tenant B para producto microcrédito",
"notify_via": ["push", "email"],
"expires_in_min": 30
}

Response 202 Accepted:

{
"request_id": "req_01J8H5...",
"status": "awaiting_consent",
"acai_debited": 5
}
{
"event_id": "5f8d1c3e-7a01-4d6e-8a9d-5e8c8b5fffff",
"tenant_id": "8a7b6c5d-1234-5678-9abc-def012345678",
"module": "bmonkey",
"event_type": "bmonkey.presentation.granted",
"occurred_at":"2026-05-25T14:01:30Z",
"payload": {
"request_id": "req_01J8H5...",
"presentation_id":"prs_01J8H6...",
"subject_id": "0c4b5b32-3a89-4a45-9c2c-1a8c47e91234",
"acr": "LoA3",
"issued_at": "2026-03-12T15:00:00Z",
"claims": {
"full_name": "Camila Rodríguez",
"document": "1023456789",
"address": "Calle 100 #15-22, Bogotá"
},
"valid_until": "2026-06-24T14:01:30Z",
"acai_debited": 32,
"revenue_share": { "origin_tenant_credit": 12 }
}
}
{
"event_type": "bmonkey.presentation.denied",
"payload": {
"request_id": "req_01J8H5...",
"reason": "user_denied",
"fallback_recommended": "kyc_from_scratch",
"acai_debited": 5
}
}

Si el usuario rechaza, no se debitan los 32 Açaí — solo los 5 del request. Tu app debe ofrecer el KYC clásico como camino alternativo.

EventoCuándo
bmonkey.subject.createdSubmission recibida, antes de procesar
bmonkey.subject.verifiedKYC aprobado (biometría OK, documento válido)
bmonkey.subject.rejectedKYC rechazado (con reason específico)
bmonkey.auth.login.succeededSesión OIDC abierta (con acr indicando LoA)
bmonkey.auth.login.failedIntento fallido (incluye razón: bad_password / mfa_expired)
bmonkey.auth.session.revokedLogout o expiración
bmonkey.presentation.requestedUn tenant solicitó reuso del wallet
bmonkey.presentation.grantedUsuario aprobó — credenciales entregadas
bmonkey.presentation.deniedUsuario rechazó la solicitud de reuso
bmonkey.wallet.grant.revokedUsuario revocó un acceso vigente

Cada evento se entrega al endpoint registrado en /tenants/{id}/webhook-endpoints/bmonkey y vive bajo el subject NATS interno bmonkey.subject.<event> para integración entre apps.

Bmonkey devuelve application/problem+json (RFC 7807):

{
"type": "https://digital-jungle.bjungle.com/errors/biometric-low-score",
"title": "Biometric score below threshold",
"status": 422,
"detail": "Liveness score 0.42 < umbral 0.70 del tenant",
"instance": "/v1/kyc/submissions/01J8FXY...",
"errors": [
{ "path": "biometric.liveness_score", "detail": "0.42 < 0.70" }
]
}
StatusCausa típica
400 Bad RequestPayload malformado o template_code inexistente
401 UnauthorizedX-API-Key ausente o inválida
402 Payment RequiredSin Açaí disponibles ni capacidad de overage
409 ConflictIdempotency-Key repetida con payload distinto
422 UnprocessableValidación de negocio (umbrales biométricos, documento inválido)
429 Too Many RequestsRate limit del plan excedido