Skip to main content

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?

Real-Time Monitoring

Receive instant notifications about new or updated transactions

Fraud Detection

Implement additional security checks in real-time

Workflow Automation

Trigger automated processes based on transaction activity

Audit and Compliance

Keep audit logs synchronized across all your systems

Available Events

Gu1 sends webhooks for the following transaction events:
Event TypeDescriptionWhen Triggered
transaction.createdTransaction createdWhen a new transaction is recorded in the system
transaction.updatedTransaction updatedWhen a transaction’s status or information is updated
The transaction.created and transaction.updated events are currently in development and will be activated soon. This documentation is available to prepare your integration.

Event Payload Structure

All transaction webhook events follow this standard structure:
{
  "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

event
string
The event type (e.g., transaction.created)
timestamp
string
ISO 8601 timestamp when the event occurred
organizationId
string
Your organization ID
payload.transactionId
string
The transaction’s UUID in Gu1
payload.externalId
string
Your external ID for the transaction
payload.type
string
Transaction type: payment, transfer, withdrawal, etc.
payload.status
string
Current transaction status: CREATED, PROCESSING, SUSPENDED, SENT, SUCCESSFUL, DECLINED, REFUNDED, EXPIRED
payload.amount
number
Transaction amount in original currency
payload.currency
string
ISO 4217 currency code (e.g., USD, EUR, MXN)

Event-Specific Payloads

transaction.created

Sent when a new transaction is recorded in the system.
{
  "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).
{
  "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.

Code Examples

Node.js - Handling Transaction Events

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;
  }
}

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

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
            )

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

The webhook includes externalId which is the ID you provided when creating the transaction. Use it to look up the transaction in your database.
const transaction = await db.findTransaction({
  externalId: data.externalId
});
Save Gu1’s transactionId in your database. This allows you to query transaction details later if needed.
await db.updateTransaction(transaction.id, {
  gu1TransactionId: data.transactionId,
  status: data.status
});
You may receive the same webhook multiple times. Use the transactionId and event to ensure you process each event only once.
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
  );
}
Always return a 200 status code as quickly as possible to confirm receipt. Process the webhook asynchronously if needed.
app.post('/webhooks/transactions', async (req, res) => {
  // Confirm immediately
  res.status(200).send('OK');

  // Process asynchronously
  processWebhook(req.body).catch(console.error);
});
Always verify the X-Webhook-Signature header to ensure the webhook is authentic. See the security guide for details.
const signature = req.headers['x-webhook-signature'];
if (!verifySignature(req.rawBody, signature, secret)) {
  return res.status(401).json({ error: 'Invalid signature' });
}

Troubleshooting

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
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 for proper implementation.
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.

Next Steps