> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gu1.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Eventos de Webhook de Transacciones

> Reciba notificaciones en tiempo real cuando se creen o actualicen transacciones — con eventos webhook de gu1 para integración downstream en tiempo real.

## Descripción General

Los eventos de webhook de transacciones le permiten recibir notificaciones en tiempo real cuando se crean o actualizan transacciones en su organización. Gu1 envía automáticamente solicitudes HTTP POST a su endpoint de webhook configurado, permitiéndole automatizar flujos de trabajo de monitoreo de transacciones, detección de fraude y cumplimiento normativo.

## ¿Por Qué Usar Webhooks de Transacciones?

<CardGroup cols={2}>
  <Card title="Monitoreo en Tiempo Real" icon="bolt">
    Reciba notificaciones instantáneas sobre transacciones nuevas o actualizadas
  </Card>

  <Card title="Detección de Fraude" icon="shield-halved">
    Implemente verificaciones de seguridad adicionales en tiempo real
  </Card>

  <Card title="Automatización de Workflows" icon="robot">
    Active procesos automáticos basados en actividad transaccional
  </Card>

  <Card title="Auditoría y Cumplimiento" icon="clipboard-check">
    Mantenga registros de auditoría sincronizados en todos sus sistemas
  </Card>
</CardGroup>

## Eventos Disponibles

Gu1 envía webhooks para los siguientes eventos de transacciones:

| Tipo de Evento               | Descripción             | Cuándo se Activa                                                                                                                     |
| ---------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `transaction.created`        | Transacción creada      | Cuando se registra una nueva transacción en el sistema                                                                               |
| `transaction.updated`        | Transacción actualizada | Cuando se actualiza el estado u otros campos (instantánea completa en el payload)                                                    |
| `transaction.status_changed` | Cambio de estado        | Cuando cambia el **estado** de la transacción (panel, API u otros flujos). Payload reducido centrado en `previousStatus` → `status`. |

<Note>
  Los eventos `transaction.created` y `transaction.updated` están actualmente en desarrollo y se activarán próximamente. La documentación está disponible para preparar su integración.

  **Disponible hoy:** `transaction.status_changed` se emite cuando cambia el estado. Gu1 también puede enviar **`transaction.updated`** en el mismo cambio (payload más rico con montos, origen/destino, etc.). Suscríbase a uno o a ambos según necesite la instantánea completa o solo la transición.
</Note>

## Estructura del Payload de Evento

Todos los eventos de webhook de transacciones siguen esta estructura estándar:

```json theme={null}
{
  "event": "transaction.created",
  "timestamp": "2025-01-29T15:30:00Z",
  "organizationId": "org-123",
  "payload": {
    "transactionId": "550e8400-e29b-41d4-a716-446655440000",
    "externalId": "txn_abc123",
    "type": "payment",
    "status": "CREATED",
    "amount": 5000.00,
    "currency": "USD"
    // ... campos específicos del evento
  }
}
```

### Campos Comunes del Payload

<ResponseField name="event" type="string">
  El tipo de evento (por ejemplo, `transaction.created`)
</ResponseField>

<ResponseField name="timestamp" type="string">
  Timestamp ISO 8601 cuando ocurrió el evento
</ResponseField>

<ResponseField name="organizationId" type="string">
  Su ID de organización
</ResponseField>

<ResponseField name="payload.transactionId" type="string">
  El ID UUID de la transacción en Gu1
</ResponseField>

<ResponseField name="payload.externalId" type="string">
  Su ID externo para la transacción
</ResponseField>

<ResponseField name="payload.type" type="string">
  Tipo de transacción: `payment`, `transfer`, `withdrawal`, etc.
</ResponseField>

<ResponseField name="payload.status" type="string">
  Estado actual de la transacción: `CREATED`, `PROCESSING`, `SUSPENDED`, `SENT`, `SUCCESSFUL`, `DECLINED`, `REFUNDED`, `EXPIRED`
</ResponseField>

<ResponseField name="payload.amount" type="number">
  Monto de la transacción en la moneda original
</ResponseField>

<ResponseField name="payload.currency" type="string">
  Código de moneda ISO 4217 (por ejemplo, `USD`, `EUR`, `MXN`)
</ResponseField>

## Payloads Específicos de Eventos

### transaction.created

Enviado cuando se registra una nueva transacción en el sistema.

```json theme={null}
{
  "event": "transaction.created",
  "timestamp": "2025-01-29T15:30:00Z",
  "organizationId": "org-123",
  "payload": {
    "transactionId": "550e8400-e29b-41d4-a716-446655440000",
    "externalId": "txn_abc123",
    "type": "payment",
    "status": "CREATED",
    "amount": 5000.00,
    "currency": "USD",
    "amountInUsd": 5000.00,
    "origin": {
      "entityId": "123e4567-e89b-12d3-a456-426614174001",
      "externalId": "customer_john",
      "name": "John Doe",
      "country": "US"
    },
    "destination": {
      "entityId": "123e4567-e89b-12d3-a456-426614174002",
      "externalId": "merchant_acme",
      "name": "ACME Corp",
      "country": "US"
    },
    "transactedAt": "2025-01-29T15:30:00Z",
    "createdAt": "2025-01-29T15:30:00Z"
  }
}
```

**Caso de uso**: Active verificaciones de fraude adicionales, actualice saldos de cuenta en tiempo real, o inicie procesos de cumplimiento.

### transaction.updated

Enviado cuando se actualiza una transacción existente (por ejemplo, cambio de estado).

```json theme={null}
{
  "event": "transaction.updated",
  "timestamp": "2025-01-29T15:35:00Z",
  "organizationId": "org-123",
  "payload": {
    "transactionId": "550e8400-e29b-41d4-a716-446655440000",
    "externalId": "txn_abc123",
    "type": "payment",
    "status": "SUCCESSFUL",
    "amount": 5000.00,
    "currency": "USD",
    "amountInUsd": 5000.00,
    "origin": {
      "entityId": "123e4567-e89b-12d3-a456-426614174001",
      "externalId": "customer_john",
      "name": "John Doe",
      "country": "US"
    },
    "destination": {
      "entityId": "123e4567-e89b-12d3-a456-426614174002",
      "externalId": "merchant_acme",
      "name": "ACME Corp",
      "country": "US"
    },
    "previousStatus": "PROCESSING",
    "newStatus": "SUCCESSFUL",
    "updatedAt": "2025-01-29T15:35:00Z"
  }
}
```

**Caso de uso**: Notifique a clientes sobre el estado de su transacción, actualice dashboards en tiempo real, o active flujos de trabajo post-transacción.

### transaction.status\_changed

Se envía cuando cambia el **estado** de una transacción (por ejemplo tras una actualización manual en **Monitoreo de transacciones** o vía API). El payload es compacto: nuevo y anterior estado más datos identificadores en `transaction`. Tras el cambio puede reejecutarse el motor de reglas; cuando aplique, **`rulesExecutionSummary`** refleja el mismo tipo de resumen que la API de transacciones para esa evaluación.

```json theme={null}
{
  "event": "transaction.status_changed",
  "timestamp": "2025-01-29T15:35:00Z",
  "organizationId": "org-123",
  "payload": {
    "status": "SUCCESSFUL",
    "previousStatus": "PROCESSING",
    "transaction": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "externalId": "txn_abc123",
      "type": "payment",
      "status": "SUCCESSFUL"
    },
    "rulesExecutionSummary": {}
  }
}
```

<ResponseField name="payload.status" type="string">
  Nuevo estado tras el cambio (p. ej. `SUCCESSFUL`, `DECLINED`, `REFUNDED`).
</ResponseField>

<ResponseField name="payload.previousStatus" type="string">
  Estado anterior al cambio.
</ResponseField>

<ResponseField name="payload.transaction" type="object">
  Identificadores: `id` (UUID en Gu1), `externalId`, `type` y `status` actual.
</ResponseField>

<ResponseField name="payload.rulesExecutionSummary" type="object">
  Opcional. Presente cuando hay metadatos de evaluación de reglas para esta transición (misma forma conceptual que en respuestas API).
</ResponseField>

**Caso de uso**: Automatizar solo sobre transiciones de estado, auditoría o integraciones que requieran un webhook liviano. Prefiera **`transaction.updated`** si necesita montos, origen/destino y demás campos en la misma notificación.

## Ejemplos de Código

### Node.js - Manejo de Eventos de Transacciones

```javascript theme={null}
const express = require('express');
const crypto = require('crypto');

const app = express();

app.use(express.json({
  verify: (req, res, buf) => {
    req.rawBody = buf.toString('utf8');
  }
}));

app.post('/webhooks/transactions', async (req, res) => {
  try {
    // Verificar firma de webhook (ver guía de seguridad)
    const signature = req.headers['x-webhook-signature'];
    const webhookSecret = process.env.GU1_WEBHOOK_SECRET;

    if (!verifySignature(req.rawBody, signature, webhookSecret)) {
      console.error('Invalid webhook signature');
      return res.status(401).json({ error: 'Invalid signature' });
    }

    // Extraer datos del webhook
    const { event, timestamp, organizationId, payload } = req.body;

    console.log('Received transaction webhook:', {
      event,
      transactionId: payload.transactionId,
      status: payload.status
    });

    // Procesar el webhook basado en el tipo de evento
    await handleTransactionWebhook(event, payload);

    // Retornar 200 para confirmar recepción
    res.status(200).json({
      success: true,
      message: 'Webhook received'
    });
  } catch (error) {
    console.error('Webhook error:', error);
    res.status(500).json({
      error: error.message
    });
  }
});

async function handleTransactionWebhook(event, data) {
  const { transactionId, externalId, status, amount, currency } = data;

  // Actualizar su base de datos con la transacción de Gu1
  await db.updateTransaction(externalId, {
    gu1TransactionId: transactionId,
    status: status,
    lastUpdated: new Date()
  });

  // Realizar acciones basadas en el tipo de evento
  switch (event) {
    case 'transaction.created':
      console.log('New transaction created:', externalId);

      // Verificaciones de fraude adicionales
      if (amount > 10000) {
        await triggerHighValueReview(transactionId);
      }

      // Notificar al cliente
      await notifyCustomer(data.origin.externalId, 'transaction-created', {
        amount,
        currency,
        recipient: data.destination.name
      });
      break;

    case 'transaction.updated':
      console.log('Transaction status changed:', {
        externalId,
        from: data.previousStatus,
        to: data.newStatus
      });

      // Actualizar estado en su sistema
      await db.updateTransaction(externalId, {
        status: data.newStatus,
        statusChangedAt: new Date()
      });

      // Notificar sobre transacción completada
      if (data.newStatus === 'SUCCESSFUL') {
        await notifyCustomer(data.origin.externalId, 'transaction-completed', {
          amount,
          currency,
          recipient: data.destination.name
        });
      }

      // Manejar transacciones rechazadas
      if (data.newStatus === 'DECLINED') {
        await notifyCustomer(data.origin.externalId, 'transaction-declined', {
          amount,
          currency
        });
        await logDeclinedTransaction(transactionId, externalId);
      }
      break;

    case 'transaction.status_changed':
      console.log('Transaction status transition:', {
        gu1Id: data.transaction?.id,
        externalId: data.transaction?.externalId,
        from: data.previousStatus,
        to: data.status,
      });
      await db.updateTransaction(data.transaction.externalId, {
        status: data.status,
        statusChangedAt: new Date(),
      });
      break;
  }
}

function verifySignature(rawBody, signature, secret) {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');

  return signature === expectedSignature;
}

app.listen(3000);
```

### Python - Manejo de Eventos de Transacciones

```python theme={null}
from flask import Flask, request, jsonify
import hmac
import hashlib
from datetime import datetime

app = Flask(__name__)

@app.route('/webhooks/transactions', methods=['POST'])
def handle_transaction_webhook():
    try:
        # Verificar firma de webhook
        signature = request.headers.get('X-Webhook-Signature')
        webhook_secret = os.getenv('GU1_WEBHOOK_SECRET')

        if not verify_signature(request.data, signature, webhook_secret):
            return jsonify({'error': 'Invalid signature'}), 401

        # Procesar webhook
        data = request.json
        event = data['event']
        payload = data['payload']

        print(f'Received transaction webhook: {event}')

        # Manejar evento
        handle_transaction_event(event, payload)

        return jsonify({
            'success': True,
            'message': 'Webhook received'
        }), 200

    except Exception as e:
        print(f'Webhook error: {str(e)}')
        return jsonify({'error': str(e)}), 500

def handle_transaction_event(event, data):
    transaction_id = data['transactionId']
    external_id = data['externalId']

    # Actualizar base de datos
    db.update_transaction(
        external_id=external_id,
        gu1_transaction_id=transaction_id,
        status=data['status'],
        last_updated=datetime.now()
    )

    # Manejar diferentes eventos
    if event == 'transaction.created':
        # Verificaciones de fraude
        if data['amount'] > 10000:
            trigger_high_value_review(transaction_id)

        # Notificar cliente
        notify_customer(
            data['origin']['externalId'],
            'transaction-created',
            data
        )

    elif event == 'transaction.updated':
        # Registrar cambio de estado
        log_status_change(
            external_id,
            data.get('previousStatus'),
            data['status']
        )

        # Notificar sobre completada
        if data['status'] == 'SUCCESSFUL':
            notify_customer(
                data['origin']['externalId'],
                'transaction-completed',
                data
            )

        # Manejar rechazadas
        elif data['status'] == 'DECLINED':
            notify_customer(
                data['origin']['externalId'],
                'transaction-declined',
                data
            )

    elif event == 'transaction.status_changed':
        tx = data['transaction']
        log_status_change(
            tx['externalId'],
            data['previousStatus'],
            data['status'],
        )

def verify_signature(raw_body, signature, secret):
    expected = hmac.new(
        secret.encode('utf-8'),
        raw_body,
        hashlib.sha256
    ).hexdigest()
    return signature == expected

if __name__ == '__main__':
    app.run(port=3000)
```

## Mejores Prácticas

<AccordionGroup>
  <Accordion title="Use externalId para Búsqueda">
    El webhook incluye `externalId` que es el ID que usted proporcionó al crear la transacción. Úselo para buscar la transacción en su base de datos.

    ```javascript theme={null}
    const transaction = await db.findTransaction({
      externalId: data.externalId
    });
    ```
  </Accordion>

  <Accordion title="Almacene IDs de Transacción de Gu1">
    Guarde el `transactionId` de Gu1 en su base de datos. Esto le permite consultar detalles de la transacción más tarde si es necesario.

    ```javascript theme={null}
    await db.updateTransaction(transaction.id, {
      gu1TransactionId: data.transactionId,
      status: data.status
    });
    ```
  </Accordion>

  <Accordion title="Maneje Idempotencia">
    Puede recibir el mismo webhook múltiples veces. Use el `transactionId` y el `event` para garantizar que procese cada evento solo una vez.

    ```javascript theme={null}
    async function handleWebhook(webhook) {
      const alreadyProcessed = await db.checkWebhookProcessed(
        webhook.payload.transactionId,
        webhook.event
      );

      if (alreadyProcessed) {
        return; // Saltar duplicado
      }

      // Procesar webhook
      await processTransaction(webhook.payload);

      // Marcar como procesado
      await db.markWebhookProcessed(
        webhook.payload.transactionId,
        webhook.event
      );
    }
    ```
  </Accordion>

  <Accordion title="Retorne 200 Rápidamente">
    Siempre retorne un código de estado 200 lo más rápido posible para confirmar recepción. Procese el webhook asincrónicamente si es necesario.

    ```javascript theme={null}
    app.post('/webhooks/transactions', async (req, res) => {
      // Confirmar inmediatamente
      res.status(200).send('OK');

      // Procesar asincrónicamente
      processWebhook(req.body).catch(console.error);
    });
    ```
  </Accordion>

  <Accordion title="Verifique Firmas">
    Siempre verifique el header `X-Webhook-Signature` para garantizar que el webhook sea auténtico. Vea la [guía de seguridad](/es/webhooks/security) para detalles.

    ```javascript theme={null}
    const signature = req.headers['x-webhook-signature'];
    if (!verifySignature(req.rawBody, signature, secret)) {
      return res.status(401).json({ error: 'Invalid signature' });
    }
    ```
  </Accordion>
</AccordionGroup>

## Solución de Problemas

<AccordionGroup>
  <Accordion title="No Recibo Webhooks">
    **Verificar estos elementos:**

    * URL del webhook es públicamente accesible vía HTTPS
    * Webhook está configurado y **habilitado** en el dashboard
    * Suscrito a los tipos de eventos correctos
    * Endpoint retorna código de estado 200 dentro de 30 segundos
    * Verificar logs del servidor para solicitudes recibidas
    * Los eventos de transacciones están actualmente en desarrollo - confirme que estén activados para su organización
  </Accordion>

  <Accordion title="Verificación de Firma Fallando">
    **Causas comunes:**

    * Usar secret incorrecto (verificar dashboard para secret actual)
    * Verificar firma en JSON parseado en vez de body raw
    * Secret no guardado correctamente después de crear webhook
    * Problemas de codificación (garantizar UTF-8)

    Vea la [guía de seguridad](/es/webhooks/security) para implementación adecuada.
  </Accordion>

  <Accordion title="Recibiendo Webhooks Duplicados">
    Este es un comportamiento normal. Los webhooks pueden enviarse múltiples veces debido a problemas de red, timeouts o reintentos.

    **Siempre implemente idempotencia** usando el `transactionId` y tipo de `event` del webhook.
  </Accordion>
</AccordionGroup>

## Próximos Pasos

<CardGroup cols={2}>
  <Card title="Eventos de Entidades" icon="user" href="/es/webhooks/events/entity-events">
    Manejar eventos de ciclo de vida de entidades
  </Card>

  <Card title="Eventos KYC" icon="id-card" href="/es/webhooks/events/kyc-events">
    Procesar actualizaciones de verificación KYC
  </Card>

  <Card title="Seguridad de Webhooks" icon="shield" href="/es/webhooks/security">
    Asegurar sus endpoints de webhook
  </Card>

  <Card title="Configuración" icon="gear" href="/es/webhooks/configuration">
    Configurar ajustes de webhook
  </Card>
</CardGroup>
