Webhooks API

Receive real-time notifications when payment statuses change. PayDirect sends HMAC-SHA256 signed POST requests to your registered endpoints.

Event Types
Subscribe to the events your application needs
EventDescription
payment.createdA new payment intent was created
payment.detectedAn incoming on-chain transfer was detected
payment.confirmedRequired block confirmations were met
payment.forwardedNet amount sent to merchant, fee sent to treasury
payment.failedSettlement failed due to an error
payment.expiredPayment expired without receiving funds
POST
/api/v1/webhooks
Register a new webhook endpoint

Request Body

FieldTypeRequiredDescription
urlstringYesHTTPS endpoint that will receive POST requests
eventsstring[]YesNon-empty array of event types to subscribe to

Example

curl -X POST https://www.paydirect.com/api/v1/webhooks \
  -H "Authorization: Bearer pd_test_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.com/webhooks/paydirect",
    "events": ["payment.confirmed", "payment.forwarded", "payment.failed"]
  }'

Response (201 Created)

{
  "webhook": {
    "id": 7,
    "url": "https://yourapp.com/webhooks/paydirect",
    "events": ["payment.confirmed", "payment.forwarded", "payment.failed"],
    "active": true,
    "created_at": "2026-02-16T14:36:22Z",
    "signingSecret": "whsec_a1b2c3d4e5f6..."
  }
}

The signingSecret is only returned once at creation. Store it securely for signature verification.

GET
/api/v1/webhooks
List all registered webhooks
curl https://www.paydirect.com/api/v1/webhooks \
  -H "Authorization: Bearer pd_test_abc123..."

Response (200 OK)

{
  "webhooks": [
    {
      "id": 7,
      "url": "https://yourapp.com/webhooks/paydirect",
      "events": ["payment.confirmed", "payment.forwarded", "payment.failed"],
      "active": true,
      "created_at": "2026-02-16T14:36:22Z"
    }
  ]
}
DELETE
/api/v1/webhooks/:id
Remove a webhook endpoint
curl -X DELETE https://www.paydirect.com/api/v1/webhooks/7 \
  -H "Authorization: Bearer pd_test_abc123..."

Response (200 OK)

{ "success": true }
Webhook Payload Format
What your endpoint receives on each event

HTTP Headers

HeaderDescription
X-PayDirect-SignatureHMAC-SHA256 hex digest of the request body
X-PayDirect-EventEvent type (e.g. payment.forwarded)
X-PayDirect-DeliveryUnique delivery ID for deduplication

Example Payload

{
  "event": "payment.forwarded",
  "payment": {
    "id": "a1b2c3d4-...",
    "status": "forwarded",
    "token_symbol": "USDC",
    "gross_amount": "100.00",
    "fee_amount": "1.50",
    "net_amount": "98.50",
    "merchant_wallet": "0xYourBaseAddress...",
    "environment": "live",
    "created_at": "2026-02-16T14:36:22Z"
  }
}
Signature Verification
Always verify the HMAC-SHA256 signature before processing webhooks

TypeScript (using SDK)

import { PayDirectClient } from "@paydirect/sdk";

app.post("/webhooks/paydirect", (req, res) => {
  const signature = req.headers["x-paydirect-signature"];
  const body = JSON.stringify(req.body);

  const isValid = PayDirectClient.verifyWebhookSignature(
    process.env.WEBHOOK_SIGNING_SECRET,  // "whsec_..."
    body,
    signature
  );

  if (!isValid) {
    return res.status(401).json({ error: "Invalid signature" });
  }

  const event = req.headers["x-paydirect-event"];
  if (event === "payment.forwarded") {
    // Fulfill the order
  }

  res.status(200).json({ received: true });
});

Python (using SDK)

from paydirect.webhooks import verify_signature
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route("/webhooks/paydirect", methods=["POST"])
def handle_webhook():
    signature = request.headers.get("X-PayDirect-Signature")
    body = request.get_data(as_text=True)

    if not verify_signature(
        secret=WEBHOOK_SIGNING_SECRET,
        payload=body,
        signature=signature
    ):
        return jsonify({"error": "Invalid signature"}), 401

    event = request.headers.get("X-PayDirect-Event")
    if event == "payment.forwarded":
        # Fulfill the order
        pass

    return jsonify({"received": True}), 200

Raw Node.js (no SDK)

import crypto from "crypto";

function verifyWebhook(secret, body, signature) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(body)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(expected, "hex"),
    Buffer.from(signature, "hex")
  );
}
Best Practices
  • Always verify the X-PayDirect-Signature header before processing any webhook payload.
  • Use the X-PayDirect-Delivery header for idempotent processing to handle retries safely.
  • Return a 200 status quickly. Process heavy work asynchronously.
  • PayDirect retries failed deliveries (non-2xx responses) with exponential backoff.
  • Subscribe only to the events you need. Use payment.forwarded as the primary signal for order fulfillment.