Webhook Setup & Configuration

Receive real-time payment notifications via HTTP POST callbacks

Webhooks allow GriffNode to push payment events to your server the moment they occur, no polling required. When a payment completes, expires, or detects a partial payment, we send a signed HTTP POST to your configured endpoint.

Creating a Webhook

  1. Go to Dashboard → Webhooks → Add Endpoint
  2. Enter your endpoint URL (must be publicly accessible HTTPS)
  3. Select the events you want to receive
  4. Copy your webhook secret, it is only shown once
Save your webhook secret immediately.

The secret is shown only at creation time. If you lose it, you must regenerate it from the dashboard, which will invalidate the old secret.

Webhook Request Headers

Header Description
X-GriffNode-Signature HMAC-SHA256 signature of the raw request body, formatted as sha256=<hex>
X-GriffNode-Event Event type, e.g. payment.completed
X-Webhook-ID ID of your webhook endpoint, e.g. WHK-XXXXXXXX. The same on every delivery; to detect duplicates use the payload's id.
X-GriffNode-Attempt Delivery attempt number: 1 for the first send, higher on retries.
Content-Type application/json

Webhook Payload

Webhook POST body
{
  "id": "evt_6f1c2d3e-4b5a-4c7d-8e9f-0a1b2c3d4e5f",
  "event": "payment.completed",
  "timestamp": "2026-03-08T10:30:00Z",
  "transaction_id": "MTX-A1B2C3D4",
  "status": "completed",
  "currency_crypto": "BTC",
  "amount_fiat": "25.00",
  "amount_crypto": "0.00025431"
}

Responding to Webhooks

Your endpoint must return HTTP 2xx within 10 seconds. Any other status, a timeout or a connection error counts as a failed delivery, and GriffNode retries automatically with growing gaps: after about 10 seconds, 1 minute, 5 minutes, 30 minutes, then 1, 2, 4 and 8 hours, for up to 24 hours after the event. After that the delivery is marked failed.

  • Handle duplicates. A retry can arrive even if you processed the first attempt (for example when your response timed out). Every event has a stable id in the payload that is identical on every retry: store it and skip events you've already handled.
  • Don't rely on order. A retried earlier event (say payment.pending) can arrive after a later one (payment.completed). Use the payload's timestamp or check the current state with GET /v1/transactions/{transaction_id}.
  • Reconcile money. For anything that affects money, confirm with GET /v1/transactions/{transaction_id} before acting.
Always verify signatures.

Never process a webhook without first verifying the X-GriffNode-Signature header. See Signature Verification.

Example Receiver (Node.js)

Express.js webhook handler
import crypto from 'crypto';
import express from 'express';

const app = express();

// IMPORTANT: use raw body for signature verification
app.post('/webhooks/griffnode', express.raw({ type: 'application/json' }), async (req, res) => {
  const sig = req.headers['x-griffnode-signature'] || '';
  const secret = process.env.WEBHOOK_SECRET;

  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(req.body)
    .digest('hex');

  if (sig !== expected) {
    return res.status(400).send('Invalid signature');
  }

  const event = JSON.parse(req.body);
  if (await alreadyProcessed(event.id)) return res.sendStatus(200); // retries reuse the same id

  switch (event.event) {
    case 'payment.completed':
      // Fulfil the order
      console.log('Payment completed for', event.transaction_id);
      break;
    case 'payment.partial':
      console.log('Partial payment for', event.transaction_id);
      break;
    case 'payment.expired':
      console.log('Transaction expired:', event.transaction_id);
      break;
  }

  res.status(200).send('OK');
});