> ## 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.

# Transaction Webhook Events

> Receive real-time notifications when transactions are created or updated — with gu1 webhook events for real-time downstream integration.

## Overview

Transaction webhook events allow you to receive real-time notifications when transactions are created or updated in your organization. Gu1 automatically sends HTTP POST requests to your configured webhook endpoint, enabling you to automate transaction monitoring workflows, fraud detection, and regulatory compliance.

## Why Use Transaction Webhooks?

<CardGroup cols={2}>
  <Card title="Real-Time Monitoring" icon="bolt">
    Receive instant notifications about new or updated transactions
  </Card>

  <Card title="Fraud Detection" icon="shield-halved">
    Implement additional security checks in real-time
  </Card>

  <Card title="Workflow Automation" icon="robot">
    Trigger automated processes based on transaction activity
  </Card>

  <Card title="Audit and Compliance" icon="clipboard-check">
    Keep audit logs synchronized across all your systems
  </Card>
</CardGroup>

## Available Events

Gu1 sends webhooks for the following transaction events:

| Event Type                   | Description         | When Triggered                                                                                                                   |
| ---------------------------- | ------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `transaction.created`        | Transaction created | When a new transaction is recorded in the system                                                                                 |
| `transaction.updated`        | Transaction updated | When a transaction's status or fields are updated (full snapshot in payload)                                                     |
| `transaction.status_changed` | Status transition   | When a transaction's **status** changes (dashboard, API, or other flows). Narrow payload focused on `previousStatus` → `status`. |

<Note>
  The `transaction.created` and `transaction.updated` events are currently in development and will be activated soon. This documentation is available to prepare your integration.

  **Available today:** `transaction.status_changed` is emitted when the status changes. Gu1 may also send **`transaction.updated`** for the same change (richer payload with amounts, origin/destination, etc.). Subscribe to one or both depending on whether you need the full transaction snapshot or only the transition.
</Note>

## Event Payload Structure

All transaction webhook events follow this standard structure:

```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"
    // ... event-specific fields
  }
}
```

### Common Payload Fields

<ResponseField name="event" type="string">
  The event type (e.g., `transaction.created`)
</ResponseField>

<ResponseField name="timestamp" type="string">
  ISO 8601 timestamp when the event occurred
</ResponseField>

<ResponseField name="organizationId" type="string">
  Your organization ID
</ResponseField>

<ResponseField name="payload.transactionId" type="string">
  The transaction's UUID in Gu1
</ResponseField>

<ResponseField name="payload.externalId" type="string">
  Your external ID for the transaction
</ResponseField>

<ResponseField name="payload.type" type="string">
  Transaction type: `payment`, `transfer`, `withdrawal`, etc.
</ResponseField>

<ResponseField name="payload.status" type="string">
  Current transaction status: `CREATED`, `PROCESSING`, `SUSPENDED`, `SENT`, `SUCCESSFUL`, `DECLINED`, `REFUNDED`, `EXPIRED`
</ResponseField>

<ResponseField name="payload.amount" type="number">
  Transaction amount in original currency
</ResponseField>

<ResponseField name="payload.currency" type="string">
  ISO 4217 currency code (e.g., `USD`, `EUR`, `MXN`)
</ResponseField>

## Event-Specific Payloads

### transaction.created

Sent when a new transaction is recorded in the system.

```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"
  }
}
```

**Use case**: Trigger additional fraud checks, update account balances in real-time, or initiate compliance processes.

### transaction.updated

Sent when an existing transaction is updated (e.g., status change).

```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"
  }
}
```

**Use case**: Notify customers about their transaction status, update dashboards in real-time, or trigger post-transaction workflows.

### transaction.status\_changed

Sent when a transaction's **status** changes (for example after a manual status update in **Transaction Monitoring** or via the API). The payload is intentionally compact: it carries the new and previous status plus identifying fields on `transaction`. After the change, the rules engine may re-run; when applicable, **`rulesExecutionSummary`** mirrors the summary returned by the transaction API for that evaluation.

```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">
  New transaction status after the change (e.g. `SUCCESSFUL`, `DECLINED`, `REFUNDED`).
</ResponseField>

<ResponseField name="payload.previousStatus" type="string">
  Status before the change.
</ResponseField>

<ResponseField name="payload.transaction" type="object">
  Snapshot identifiers: `id` (Gu1 UUID), `externalId`, `type`, and current `status`.
</ResponseField>

<ResponseField name="payload.rulesExecutionSummary" type="object">
  Optional. Present when rule evaluation metadata is available for this transition (same conceptual shape as in API responses).
</ResponseField>

**Use case**: Drive workflows that only care about status transitions, audit trails, or webhooks that must stay small. Prefer **`transaction.updated`** if you need amounts, origin/destination, and other fields in the same notification.

## Code Examples

### Node.js - Handling Transaction Events

```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 {
    // Verify webhook signature (see security guide)
    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' });
    }

    // Extract webhook data
    const { event, timestamp, organizationId, payload } = req.body;

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

    // Process webhook based on event type
    await handleTransactionWebhook(event, payload);

    // Return 200 to confirm receipt
    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;

  // Update your database with Gu1 transaction
  await db.updateTransaction(externalId, {
    gu1TransactionId: transactionId,
    status: status,
    lastUpdated: new Date()
  });

  // Perform actions based on event type
  switch (event) {
    case 'transaction.created':
      console.log('New transaction created:', externalId);

      // Additional fraud checks
      if (amount > 10000) {
        await triggerHighValueReview(transactionId);
      }

      // Notify customer
      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
      });

      // Update status in your system
      await db.updateTransaction(externalId, {
        status: data.newStatus,
        statusChangedAt: new Date()
      });

      // Notify about completed transaction
      if (data.newStatus === 'SUCCESSFUL') {
        await notifyCustomer(data.origin.externalId, 'transaction-completed', {
          amount,
          currency,
          recipient: data.destination.name
        });
      }

      // Handle declined transactions
      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 - Handling Transaction Events

```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:
        # Verify webhook signature
        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

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

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

        # Handle event
        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']

    # Update database
    db.update_transaction(
        external_id=external_id,
        gu1_transaction_id=transaction_id,
        status=data['status'],
        last_updated=datetime.now()
    )

    # Handle different events
    if event == 'transaction.created':
        # Fraud checks
        if data['amount'] > 10000:
            trigger_high_value_review(transaction_id)

        # Notify customer
        notify_customer(
            data['origin']['externalId'],
            'transaction-created',
            data
        )

    elif event == 'transaction.updated':
        # Log status change
        log_status_change(
            external_id,
            data.get('previousStatus'),
            data['status']
        )

        # Notify about completed
        if data['status'] == 'SUCCESSFUL':
            notify_customer(
                data['origin']['externalId'],
                'transaction-completed',
                data
            )

        # Handle declined
        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)
```

## Best Practices

<AccordionGroup>
  <Accordion title="Use externalId for Lookup">
    The webhook includes `externalId` which is the ID you provided when creating the transaction. Use it to look up the transaction in your database.

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

  <Accordion title="Store Gu1 Transaction IDs">
    Save Gu1's `transactionId` in your database. This allows you to query transaction details later if needed.

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

  <Accordion title="Handle Idempotency">
    You may receive the same webhook multiple times. Use the `transactionId` and `event` to ensure you process each event only once.

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

      if (alreadyProcessed) {
        return; // Skip duplicate
      }

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

      // Mark as processed
      await db.markWebhookProcessed(
        webhook.payload.transactionId,
        webhook.event
      );
    }
    ```
  </Accordion>

  <Accordion title="Return 200 Quickly">
    Always return a 200 status code as quickly as possible to confirm receipt. Process the webhook asynchronously if needed.

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

      // Process asynchronously
      processWebhook(req.body).catch(console.error);
    });
    ```
  </Accordion>

  <Accordion title="Verify Signatures">
    Always verify the `X-Webhook-Signature` header to ensure the webhook is authentic. See the [security guide](/en/webhooks/security) for details.

    ```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>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Not Receiving Webhooks">
    **Check these items:**

    * Webhook URL is publicly accessible via HTTPS
    * Webhook is configured and **enabled** in dashboard
    * Subscribed to correct event types
    * Endpoint returns 200 status code within 30 seconds
    * Check server logs for received requests
    * Transaction events are currently in development - confirm they are enabled for your organization
  </Accordion>

  <Accordion title="Signature Verification Failing">
    **Common causes:**

    * Using wrong secret (check dashboard for current secret)
    * Verifying signature on parsed JSON instead of raw body
    * Secret not saved correctly after webhook creation
    * Encoding issues (ensure UTF-8)

    See the [security guide](/en/webhooks/security) for proper implementation.
  </Accordion>

  <Accordion title="Receiving Duplicate Webhooks">
    This is normal behavior. Webhooks may be sent multiple times due to network issues, timeouts, or retries.

    **Always implement idempotency** using the webhook's `transactionId` and `event` type.
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Entity Events" icon="user" href="/en/webhooks/events/entity-events">
    Handle entity lifecycle events
  </Card>

  <Card title="KYC Events" icon="id-card" href="/en/webhooks/events/kyc-events">
    Process KYC verification updates
  </Card>

  <Card title="Webhook Security" icon="shield" href="/en/webhooks/security">
    Secure your webhook endpoints
  </Card>

  <Card title="Configuration" icon="gear" href="/en/webhooks/configuration">
    Configure webhook settings
  </Card>
</CardGroup>
