Skip to main content
Gwop sends webhook events to your configured URL when invoice status changes. Each event is signed with HMAC-SHA256 so you can verify authenticity.

Event types

EventTrigger
invoice.paidInvoice was paid successfully
invoice.expiredInvoice expired before payment
invoice.canceledInvoice was canceled by the merchant

Verify and handle

import express from 'express';
import { GwopCheckout } from 'gwop-checkout';

const gwop = new GwopCheckout({
  merchantApiKey: process.env.GWOP_CHECKOUT_API_KEY,
});

const app = express();

app.post(
  '/webhooks/gwop',
  express.raw({ type: 'application/json' }),
  async (req, res) => {
    try {
      const event = gwop.webhooks.constructEvent(
        req.body.toString('utf8'),
        req.header('x-gwop-signature'),
        process.env.GWOP_WEBHOOK_SECRET!,
      );

      await gwop.webhooks.dispatch(event, {
        invoicePaid: async (evt) => {
          await fulfillOrder(evt.data.invoice_id);
        },
        invoiceExpired: async (evt) => {
          await cancelPendingOrder(evt.data.invoice_id);
        },
        invoiceCanceled: async (evt) => {
          await cancelPendingOrder(evt.data.invoice_id);
        },
      });

      return res.json({ ok: true });
    } catch {
      return res.status(401).json({ error: 'invalid_signature' });
    }
  },
);

Event payload

{
  event_id: string;         // Unique event ID — use for deduplication
  event_type: string;       // 'invoice.paid' | 'invoice.expired' | 'invoice.canceled'
  event_version: number;    // Monotonic per-invoice version
  created_at: string;       // ISO 8601 timestamp
  data: {
    invoice_id: string;     // inv_...
    status: string;         // 'PAID' | 'EXPIRED' | 'CANCELED'
    amount_usdc: number;    // Amount in USDC minor units
    currency: 'USDC';
    tx_hash?: string;       // On-chain transaction hash (when paid)
    tx_signature?: string;  // Solana transaction signature (when paid on Solana)
    payment_chain?: string; // 'solana' | 'base' (when paid)
    paid_at?: string;       // ISO 8601 (when paid)
    payer_wallet?: string;  // Payer address (when paid)
  }
}

Important notes

  • Deduplicate by event_id — webhooks may be delivered more than once.
  • Respond with 2xx quickly — long processing should happen asynchronously.
  • Use raw body for verification — JSON parsing before constructEvent will break signature checks.
  • Signature tolerance is 5 minutes by default. Customize with the tolerance option (in seconds).