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
| Event | Description |
|---|---|
payment.created | A new payment intent was created |
payment.detected | An incoming on-chain transfer was detected |
payment.confirmed | Required block confirmations were met |
payment.forwarded | Net amount sent to merchant, fee sent to treasury |
payment.failed | Settlement failed due to an error |
payment.expired | Payment expired without receiving funds |
POST
/api/v1/webhooks
Register a new webhook endpoint
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | HTTPS endpoint that will receive POST requests |
events | string[] | Yes | Non-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
| Header | Description |
|---|---|
X-PayDirect-Signature | HMAC-SHA256 hex digest of the request body |
X-PayDirect-Event | Event type (e.g. payment.forwarded) |
X-PayDirect-Delivery | Unique 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}), 200Raw 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-Signatureheader before processing any webhook payload. - Use the
X-PayDirect-Deliveryheader for idempotent processing to handle retries safely. - Return a
200status quickly. Process heavy work asynchronously. - PayDirect retries failed deliveries (non-2xx responses) with exponential backoff.
- Subscribe only to the events you need. Use
payment.forwardedas the primary signal for order fulfillment.
