Skip to content

Manual del Servicio de Webhooks de Kuenta

Este documento explica cómo configurar y utilizar los webhooks en Kuenta, incluyendo detalles sobre la estructura de datos disponible, las funciones de plantilla que se pueden utilizar y ejemplos prácticos.

Descripción General

Los webhooks en Kuenta permiten notificar a sistemas externos cuando ocurren eventos específicos relacionados con créditos. Cada webhook:

  • Se identifica por un "tag" único
  • Tiene una URL de destino donde se enviará la solicitud HTTP
  • Puede incluir autenticación
  • Utiliza plantillas para personalizar el cuerpo de la solicitud
  • Es activado por acciones específicas en el flujo de crédito

Estructura de Datos

Webhook

go
type Webhook struct {
    ID                 uuid.UUID
    Tag                string          // Identificador único del webhook
    Url                string          // URL de destino
    Archived           bool            // Si está archivado/inactivo
    RequestHeader      json.RawMessage // Cabeceras HTTP para la solicitud
    RequestBody        string          // Plantilla para el cuerpo de la solicitud
    EntityID           uuid.UUID       // ID de la organización propietaria
    AuthEnabled        bool            // Si la autenticación está habilitada
    AuthURL            string          // URL para autenticación
    AuthMethod         string          // Método HTTP para autenticación
    TokenHeader        bool            // Si el token viene en cabecera
    AuthRequestBody    string          // Cuerpo para solicitud de autenticación
    AuthRequestHeader  json.RawMessage // Cabeceras para autenticación
    AuthHeader         string          // Nombre de cabecera para token
    TokenType          string          // Tipo de token (Bearer, Basic, etc.)
    TokenSourceKey     string          // Clave para obtener token de respuesta
}

WebhookLog

go
type WebhookLog struct {
    ID                uuid.UUID
    ResponseDump      string           // Respuesta completa para registro
    RequestDump       string           // Solicitud completa para registro
    WebhookID         uuid.UUID        // Webhook asociado a este log
    CreditStatusLogID *uuid.UUID       // Log de estado de crédito asociado
    CreditID          *uuid.UUID       // Crédito asociado
    Webhook           *Webhook         // Webhook completo
    Status            WebhookLogStatus // Estado del webhook (éxito, error, etc.)
    ErrorMessage      string           // Mensaje de error si lo hay
}

Variables Disponibles en Plantillas

Cuando se activa un webhook, tiene acceso al objeto Credit completo con todos los datos del crédito:

.Credit.ID                  - ID del crédito
.Credit.Status              - Estado del crédito (ApprovedCredit, RejectedCredit, etc.)
.Credit.Amount              - Monto del crédito
.Credit.Principal           - Monto principal del crédito
.Credit.Rate                - Tasa de interés
.Credit.Term                - Plazo del crédito
.Credit.Time                - Duración del crédito en días
.Credit.Frequency           - Frecuencia de pago
.Credit.CreatedAt           - Fecha de creación
.Credit.UpdatedAt           - Fecha de actualización
.Credit.StartDate           - Fecha de inicio
.Credit.ApprovedAt          - Fecha de aprobación
.Credit.CancelledAt         - Fecha de cancelación
.Credit.Debtor              - Objeto del deudor
.Credit.Creditor            - Objeto del acreedor
.Credit.CreditLine          - Información de la línea de crédito
.Credit.CreditAnalysis      - Análisis de crédito y puntuación
.Credit.Installments        - Cuotas del crédito

Datos del Deudor y Acreedor

.Credit.Debtor.ID           - ID del deudor
.Credit.Debtor.Phone        - Teléfono del deudor
.Credit.Debtor.Profile      - Perfil del deudor
.Credit.Debtor.Profile.Natural - Información personal (para persona natural)
.Credit.Debtor.Profile.Legal   - Información empresarial (para persona jurídica)

.Credit.Creditor.ID         - ID del acreedor
.Credit.Creditor.Phone      - Teléfono del acreedor
.Credit.Creditor.Profile    - Perfil del acreedor

Datos de Perfil (Persona Natural)

.Credit.Debtor.Profile.Natural.FirstName    - Nombre
.Credit.Debtor.Profile.Natural.LastName     - Apellido
.Credit.Debtor.Profile.Natural.Email        - Correo electrónico
.Credit.Debtor.Profile.Natural.IDType       - Tipo de documento
.Credit.Debtor.Profile.Natural.IDNumber     - Número de documento
.Credit.Debtor.Profile.Natural.Birthdate    - Fecha de nacimiento
.Credit.Debtor.Profile.Natural.AddressHome  - Dirección de residencia
.Credit.Debtor.Profile.Natural.HomePhone    - Teléfono fijo
.Credit.Debtor.Profile.Natural.Gender       - Género
.Credit.Debtor.Profile.Natural.MaritalStatus - Estado civil
.Credit.Debtor.Profile.Natural.HomeType     - Tipo de vivienda
.Credit.Debtor.Profile.Natural.Stratum      - Estrato
.Credit.Debtor.Profile.Natural.IncomeMonthly - Ingresos mensuales
.Credit.Debtor.Profile.Natural.OtherIncome  - Otros ingresos
.Credit.Debtor.Profile.Natural.CustomFields - Campos personalizados

Funciones de Plantillas

Funciones Nativas de Go Templates

Las plantillas de webhook admiten todas las funciones estándar de Go Templates:

{ { if }}...{ { else }}...{ { end }}   - Condiciones
{ { range .Items }}...{ { end }}      - Ciclos
{ { with .Object }}...{ { end }}      - Establecer contexto
{ { index .Array 1 }}                - Acceso a índice de array
{ { .Field }}                        - Acceso a campo

Biblioteca Sprig

Las plantillas de webhooks incluyen todas las funciones de la biblioteca Sprig (v3), que proporciona una gran cantidad de utilidades. Según el análisis del código fuente en domain/webhook.go, la biblioteca Sprig se incorpora directamente al procesamiento de plantillas mediante:

go
functions := Funcs()
sprigFunctions := sprig.FuncMap()

// Parse body
webhookBody, err := template.New("test").Funcs(sprigFunctions).Funcs(functions).Parse(body)

La implementación confirma que todas las funciones de Sprig están completamente disponibles en webhooks, lo que permite transformaciones de datos complejas.

Para una lista completa de funciones de Sprig, consulta la documentación oficial de Sprig.

Algunas de las funciones más útiles de Sprig para webhooks son:

{ { toJson . }}                      - Convierte el objeto a formato JSON
{ { toYaml . }}                      - Convierte el objeto a formato YAML
{ { trim .String }}                  - Elimina espacios en blanco
{ { lower .String }}                 - Convierte a minúsculas
{ { upper .String }}                 - Convierte a mayúsculas
{ { quote .String }}                 - Añade comillas a una cadena
{ { date_format "2006-01-02" .Date }} - Formatea una fecha
{ { dict "key" "value" }}            - Crea un diccionario
{ { uuidv4 }}                        - Genera un UUID v4
{ { now }}                           - Obtiene la fecha/hora actual
{ { add 1 2 }}                       - Suma dos números
{ { sub 5 3 }}                       - Resta dos números
{ { mul 2 3 }}                       - Multiplica dos números
{ { div 6 3 }}                       - Divide dos números
{ { mod 5 2 }}                       - Obtiene el módulo (resto de división)
{ { base64 "hello" }}                - Codifica en base64
{ { sha256sum "data" }}              - Genera hash SHA-256

Funciones Específicas de Kuenta

{ { currency .Credit.Amount }}       - Formatea un valor como moneda
{ { percentage .Credit.Rate }}       - Formatea un valor como porcentaje
{ { date .Credit.CreatedAt }}        - Formatea una fecha
{ { dateAAAAMMDD .Credit.CreatedAt }} - Formatea fecha como AAAAMMDD
{ { commonData .Profile "Name" }}    - Accede a datos comunes de un perfil
{ { decimalAdd .Value1 .Value2 }}    - Suma valores decimales
{ { customFields .Object.CustomFields "field_name" }} - Obtiene campo personalizado
{ { allReferences .Personal .Family .Commercial }} - Combina referencias

Ejemplos de Uso de Plantillas

Ejemplo de Notificación de Aprobación de Crédito

json
{
  "credit_id": "{ {.Credit.ID}}",
  "status": "{ {.Credit.Status}}",
  "approved_at": "{ {.Credit.ApprovedAt | date}}",
  "debtor": {
    "id": "{ {.Credit.Debtor.ID}}",
    "name": "{ {.Credit.Debtor.Profile.Natural.FirstName}} { {.Credit.Debtor.Profile.Natural.LastName}}",
    "document": "{ {.Credit.Debtor.Profile.Natural.IDType}}-{ {.Credit.Debtor.Profile.Natural.IDNumber}}",
    "email": "{ {.Credit.Debtor.Profile.Natural.Email}}",
    "phone": "{ {.Credit.Debtor.Phone}}"
  },
  "credit_details": {
    "amount": { {.Credit.Amount}},
    "principal": { {.Credit.Principal}},
    "rate": { {.Credit.Rate}},
    "term": { {.Credit.Term}},
    "frequency": "{ {.Credit.Frequency}}",
    "installments": [
      { {range $index, $installment := .Credit.Installments}}
      { {if $index}},{ {end}}
      {
        "number": { {add $index 1}},
        "date": "{ {$installment.Date | date_format "2006-01-02"}}",
        "amount": { {$installment.Payment}},
        "principal": { {$installment.Principal}},
        "interest": { {$installment.Interest}}
      }
      { {end}}
    ]
  }
}

Ejemplos Avanzados de Transformación de Datos

Transformación de Enumeraciones a Códigos Externos

Este ejemplo muestra cómo transformar enumeraciones internas a códigos que un sistema externo pueda entender.

json
{
  "credit_id": "{ {.Credit.ID}}",
  "status": "{ {.Credit.Status}}",
  "notification_type": "credit_status_change",
  "timestamp": "{ {now | date_format "2006-01-02T15:04:05Z07:00"}}",

  "customer": {
    { {$documentTypes := dict "CC" "citizen_id" "CE" "foreign_id" "PA" "passport" "TI" "minor_id" "NIT" "tax_id"}}
    { {$customerType := .Credit.Debtor.Profile.Type}}
    { {$documentType := ""}}
    { {$documentNumber := ""}}
    { {$customerName := ""}}

    { {if eq $customerType "Natural"}}
      { {$documentType = .Credit.Debtor.Profile.Natural.IDType}}
      { {$documentNumber = .Credit.Debtor.Profile.Natural.IDNumber}}
      { {$customerName = printf "%s %s" .Credit.Debtor.Profile.Natural.FirstName .Credit.Debtor.Profile.Natural.LastName}}
    { {else}}
      { {$documentType = .Credit.Debtor.Profile.Legal.IDType}}
      { {$documentNumber = .Credit.Debtor.Profile.Legal.IDNumber}}
      { {$customerName = .Credit.Debtor.Profile.Legal.Name}}
    { {end}}

    "id_type_code": "{ {get $documentTypes $documentType "other"}}",
    "id_type_original": "{ {$documentType}}",
    "id_number": "{ {$documentNumber}}",
    "name": "{ {$customerName}}",
    "customer_type": "{ {if eq $customerType "Natural"}}INDIVIDUAL{ {else}}BUSINESS{ {end}}"
  },

  "credit": {
    "amount": { {.Credit.Amount}},
    "amount_formatted": "{ {currency .Credit.Amount}}",
    "status": {
      { {$statusMap := dict "ApprovedCredit" "APPROVED" "RejectedCredit" "REJECTED" "PendingCredit" "PENDING" "CancelledCredit" "CANCELLED" "DisbursedCredit" "DISBURSED"}}
      "code": "{ {get $statusMap .Credit.Status "UNKNOWN"}}",
      "original": "{ {.Credit.Status}}"
    },
    "interest_rate": {
      "value": { {.Credit.Rate}},
      "formatted": "{ {percentage .Credit.Rate}}"
    }
  }
}

Este ejemplo demuestra:

  • Creación de diccionarios para mapear valores
  • Uso de variables locales en la plantilla
  • Lógica condicional para diferentes tipos de clientes
  • Formateo avanzado de fechas

Generación de Datos Adicionales y Cálculos

Este ejemplo muestra cómo realizar cálculos adicionales y generar datos derivados.

json
{
  "credit_id": "{ {.Credit.ID}}",
  "timestamp": "{ {now | date_format "2006-01-02T15:04:05Z07:00"}}",
  "financial_analysis": {
    { {$totalIncome := .Credit.Debtor.Profile.Natural.IncomeMonthly}}
    { {$totalExpenses := 0.0}}
    { {$totalDebt := 0.0}}
    { {$installmentAmount := 0.0}}

    { {if .Credit.Installments}}
      { {$installmentAmount = index .Credit.Installments 0 "Payment"}}
    { {end}}

    { {range .Credit.Debtor.Expenses}}
      { {$totalExpenses = add $totalExpenses .Amount}}
    { {end}}

    { {range .Credit.Debtor.Debts}}
      { {$totalDebt = add $totalDebt .Amount}}
    { {end}}

    { {$disposableIncome := sub $totalIncome $totalExpenses}}
    { {$debtToIncomeRatio := div (mul $totalDebt 100) $totalIncome}}
    { {$installmentToIncomeRatio := div (mul $installmentAmount 100) $totalIncome}}
    { {$installmentToDisposableRatio := div (mul $installmentAmount 100) $disposableIncome}}

    "income": {
      "monthly": { {$totalIncome}},
      "formatted": "{ {currency $totalIncome}}"
    },
    "expenses": {
      "monthly": { {$totalExpenses}},
      "formatted": "{ {currency $totalExpenses}}"
    },
    "disposable_income": {
      "monthly": { {$disposableIncome}},
      "formatted": "{ {currency $disposableIncome}}"
    },
    "debt_metrics": {
      "total_debt": { {$totalDebt}},
      "debt_to_income_ratio": { {$debtToIncomeRatio | printf "%.2f"}},
      "installment_to_income_ratio": { {$installmentToIncomeRatio | printf "%.2f"}},
      "installment_to_disposable_ratio": { {$installmentToDisposableRatio | printf "%.2f"}},
      "risk_assessment": "{ {if gt $installmentToDisposableRatio 60.0}}HIGH{ {else if gt $installmentToDisposableRatio 40.0}}MEDIUM{ {else}}LOW{ {end}}"
    }
  },
  "installment_schedule": {
    "total_installments": { {len .Credit.Installments}},
    "first_installment_date": "{ {if .Credit.Installments}}{ {index .Credit.Installments 0 "Date" | date}}{ {end}}",
    "last_installment_date": "{ {if .Credit.Installments}}{ {index .Credit.Installments (sub (len .Credit.Installments) 1) "Date" | date}}{ {end}}",
    "total_principal": { {range $i, $e := .Credit.Installments}}{ {if $i}},{ {end}}{ {.Principal}}{ {end}},
    "total_interest": { {$totalInterest := 0.0}}{ {range .Credit.Installments}}{ {$totalInterest = add $totalInterest .Interest}}{ {end}}{ {$totalInterest}},
    "effective_annual_rate": { {mul .Credit.Rate 100 | printf "%.2f"}}
  },
  "hash": "{ {sha256sum (printf "%s-%s-%s" .Credit.ID .Credit.Status (now | date_format "20060102"))}}"
}

Este ejemplo muestra:

  • Cálculos complejos con variables
  • Uso de operadores lógicos para evaluación de riesgo
  • Agregación de valores de colecciones
  • Generación de hash para firma

Transformación XML para Integraciones Legacy

xml
<?xml version="1.0" encoding="UTF-8"?>
<CreditNotification>
  <Header>
    <MessageId>{ {uuidv4}}</MessageId>
    <Timestamp>{ {now | date_format "2006-01-02T15:04:05"}}</Timestamp>
    <SystemCode>KUENTA</SystemCode>
    <MessageType>CreditStatusChange</MessageType>
  </Header>
  <CreditData>
    <CreditId>{ {.Credit.ID}}</CreditId>
    <Status>{ {.Credit.Status}}</Status>
    <Amount>{ {.Credit.Amount}}</Amount>
    <Term>{ {.Credit.Term}}</Term>
    <InterestRate>{ {.Credit.Rate}}</InterestRate>
    <CreationDate>{ {.Credit.CreatedAt | date_format "2006-01-02"}}</CreationDate>
    <StatusChangeDate>{ {now | date_format "2006-01-02"}}</StatusChangeDate>
  </CreditData>
  <CustomerData>
    <PersonalInfo>
      { {$isNatural := eq .Credit.Debtor.Profile.Type "Natural"}}
      <CustomerType>{ {if $isNatural}}Individual{ {else}}Business{ {end}}</CustomerType>
      <DocumentType>{ {if $isNatural}}{ {.Credit.Debtor.Profile.Natural.IDType}}{ {else}}{ {.Credit.Debtor.Profile.Legal.IDType}}{ {end}}</DocumentType>
      <DocumentNumber>{ {if $isNatural}}{ {.Credit.Debtor.Profile.Natural.IDNumber}}{ {else}}{ {.Credit.Debtor.Profile.Legal.IDNumber}}{ {end}}</DocumentNumber>
      <FullName>{ {commonData .Credit.Debtor.Profile "Name"}}</FullName>
      <Email>{ {commonData .Credit.Debtor.Profile "Email"}}</Email>
      <Phone>{ {.Credit.Debtor.Phone}}</Phone>
    </PersonalInfo>
    <Address>
      <Street>{ {commonData .Credit.Debtor.Profile "Address"}}</Street>
      <City>{ {commonData .Credit.Debtor.Profile "AddressCity"}}</City>
    </Address>
  </CustomerData>
  <InstallmentPlan>
    { {range $idx, $installment := .Credit.Installments}}
    <Installment number="{ {add $idx 1}}">
      <DueDate>{ {$installment.Date | date_format "2006-01-02"}}</DueDate>
      <Amount>{ {$installment.Payment}}</Amount>
      <Principal>{ {$installment.Principal}}</Principal>
      <Interest>{ {$installment.Interest}}</Interest>
    </Installment>
    { {end}}
  </InstallmentPlan>
  <Signature>{ {sha256sum (printf "%s%s%s" .Credit.ID .Credit.Status (now | date_format "20060102"))}}</Signature>
</CreditNotification>

Este ejemplo muestra cómo:

  • Formatear datos para sistemas XML legacy
  • Usar condicionales para determinar el tipo de cliente
  • Generar elementos XML repetitivos con bucles
  • Crear firmas de seguridad para verificación

Configuración de Autenticación

Los webhooks pueden configurarse con diferentes métodos de autenticación:

Autenticación con Bearer Token

AuthEnabled = true
AuthURL = "https://api.ejemplo.com/auth"
AuthMethod = "POST"
AuthRequestBody = {"client_id": "mi_id", "client_secret": "mi_secreto"}
AuthHeader = "Authorization"
TokenType = "Bearer"
TokenSourceKey = "access_token"

Autenticación con Token en Cabecera

AuthEnabled = true
AuthURL = "https://api.ejemplo.com/auth"
AuthMethod = "POST"
TokenHeader = true
AuthHeader = "X-API-Token"
TokenSourceKey = "X-Auth-Token"

Mejores Prácticas

  1. Verificar existencia de datos antes de usarlos:

    { {if .Credit.ApprovedAt}}
    "approved_at": "{ {.Credit.ApprovedAt | date}}",
    { {else}}
    "approved_at": null,
    { {end}}
  2. Usar condiciones para controlar formato:

    { {if eq .Credit.Status "ApprovedCredit"}}
    "status": "APPROVED",
    { {else if eq .Credit.Status "RejectedCredit"}}
    "status": "REJECTED",
    { {else}}
    "status": "PENDING",
    { {end}}
  3. Utilizar diccionarios para mapear valores:

    { {$statusMap := dict "ApprovedCredit" "APPROVED" "RejectedCredit" "REJECTED" "PendingCredit" "PENDING"}}
    { { $status := .Credit.Status | toString}}
    "status": "{ {get $statusMap $status}}",
  4. Aprovechar la biblioteca Sprig para transformaciones de datos complejas:

    { { $data := dict "name" .Credit.Debtor.Profile.Natural.FirstName "amount" .Credit.Amount }}
    { { toJson $data }}
  5. Validar el formato de la plantilla antes de guardar con la función de prueba.

  6. Implementar manejo de errores en el sistema receptor del webhook.

Solución de Problemas

Errores Comunes

  1. Plantilla No Válida: Si recibes un error de formato de plantilla, verifica:

    • Que no haya llaves { { o }} sin cerrar
    • Que las funciones utilizadas existan y tengan la cantidad correcta de parámetros
    • Que las variables referenciadas existan en el contexto del crédito
  2. Errores de Autenticación: Si el webhook falla con errores de autenticación:

    • Verifica que la URL de autenticación sea correcta
    • Asegúrate de que las credenciales sean válidas
    • Comprueba que TokenSourceKey corresponda a la clave en la respuesta
  3. Datos Faltantes: Si faltan datos en la solicitud:

    • Utiliza la función FillEmptyFields que completa campos vacíos
    • Verifica si el campo realmente existe antes de usarlo
    • Proporciona valores predeterminados para campos opcionales

Limitaciones

  • El tamaño máximo del cuerpo de la solicitud está limitado a 10MB.
  • El tiempo de espera para la solicitud HTTP es de 30 segundos.
  • Las solicitudes fallidas no se reintentan automáticamente.
  • Solo se admiten solicitudes POST para los webhooks.
  • La autenticación tiene soporte limitado para OAuth 2.0.

Depuración

Para depurar plantillas complejas, puede utilizar la función toJson de Sprig para ver la estructura completa del objeto Credit:

{ { toJson .Credit }}

Esto mostrará todos los datos disponibles del crédito, lo que puede ser útil para identificar problemas.