Skip to main content

Webhook Integration

Webhooks enable real-time notifications to your backend when payment events occur. HashNut sends HTTP POST requests to your configured webhook URL.

Overview

Webhook Configuration

Configure your webhook URL when creating a payment order:

{
"callBackUrl": "https://your-site.com/api/payment/webhook"
}

Requirements:

  • ✅ Must be HTTPS (HTTP not supported)
  • ✅ Must return 200 OK with body "success" for successful processing
  • ✅ Should respond within 30 seconds

Webhook Events

Payment Status Updates

HashNut sends webhooks when order status changes:

EventTriggerState Value
Payment InitiatedCustomer starts payment1
Payment ConfirmingTransaction confirmed3
Payment SuccessPayment verified4
Payment FailedTransaction failed-1
Order ExpiredOrder expired-2
Order CancelledOrder cancelled-3

Webhook Request

Headers

Content-Type: application/json

Request Body

The webhook body is sent as a JSON string:

{
"payOrderId": "01KBZ292SK2GKFK97916F5EC3B",
"merchantOrderId": "e30ff306-5552-497d-9083-fd6e943dfd73",
"accessSign": "D3DE7E4002057C0EAED1BE2268DA53CC9058DCFC9DCAF50D999AF270A7B033C5",
"state": 4
}

Webhook Payload Fields

FieldTypeDescription
payOrderIdstringHashNut platform order ID
merchantOrderIdstringMerchant order ID
accessSignstringAccess signature for querying order
stateintegerOrder state (see Order States)

Webhook Processing

Note: Webhook signature verification is NOT REQUIRED. You can process webhooks directly without signature verification. This is acceptable for both development and production environments.

Webhook Response

Your webhook endpoint must return:

Success:

  • Status: 200 OK
  • Body: "success" (plain text)
  • Content-Type: text/html or text/plain

Failure:

  • Status: 400 or 500
  • Body: "failed" (plain text)

Response Examples

// Express.js
res.status(200).send('success');

// Or for errors
res.status(400).send('failed');

Webhook Processing

Processing Steps

  1. Parse JSON: Parse request body
  2. Query Order Status: Use queryPayOrderWithAccessSign to get latest order details
  3. Verify Order: Ensure order details match your records
  4. Update Database: Update order status in your system
  5. Process Business Logic: Fulfill order, send notifications, etc.
  6. Return Success: Return 200 OK with "success"

Example Implementation

app.post('/webhook', express.json(), async (req, res) => {
try {
// 1. Parse JSON
const data = req.body;
const { payOrderId, merchantOrderId, accessSign, state } = data;

// 2. Query order status (get latest details)
const orderResponse = await fetch(
'https://testnet.hashnut.io/api/v3.0.0/pay/queryPayOrderWithAccessSign',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ payOrderId, merchantOrderId, accessSign })
}
);

const orderResult = await orderResponse.json();
if (orderResult.code !== 0) {
console.error('Failed to query order:', orderResult.msg);
return res.status(400).send('failed');
}

const order = orderResult.data;

// 3. Update database based on state
if (order.state === 4) {
// Payment successful
await updateOrderStatus(merchantOrderId, 'paid', order.payTxId);
await fulfillOrder(merchantOrderId);
} else if (order.state < 0) {
// Payment failed, expired, or cancelled
await updateOrderStatus(merchantOrderId, 'failed');
}

// 4. Return success
res.send('success');
} catch (error) {
console.error('Webhook processing error:', error);
res.status(500).send('failed');
}
});

Webhook Retry Logic

HashNut automatically retries failed webhook deliveries:

  • Retry Attempts: 3 retries
  • Retry Intervals: 1 second, 5 seconds, 30 seconds (exponential backoff)
  • Timeout: 30 seconds per attempt
  • Success Criteria: Must receive 200 OK with body "success"

Retry Sequence

Idempotency

Important: Webhooks may be delivered multiple times. Implement idempotency:

  1. Check if Already Processed: Use payOrderId to check if webhook was already processed
  2. Idempotent Operations: Ensure your processing logic is idempotent
  3. Logging: Log all webhook deliveries for debugging
// Idempotency check
const existingOrder = await getOrderByPayOrderId(payOrderId);
if (existingOrder && existingOrder.status === 'paid') {
// Already processed
return res.send('success');
}

Security Best Practices

  1. Use HTTPS: Webhook endpoints must use HTTPS
  2. Validate Data: Always query order status to get latest details
  3. Handle Errors Gracefully: Return appropriate error codes
  4. Monitor Webhooks: Log all webhook deliveries for monitoring
  5. Rate Limiting: Implement rate limiting on webhook endpoints
  6. IP Whitelisting: Consider IP whitelisting (if HashNut provides IP ranges)

Testing Webhooks

Local Testing

Use tools like ngrok or localtunnel to expose your local server:

# Using ngrok
ngrok http 3000

# Use the provided HTTPS URL as your webhook URL
# https://abc123.ngrok.io/api/webhook

Webhook Testing Checklist

  • Webhook endpoint is accessible via HTTPS
  • Returns 200 OK with "success" for valid webhooks
  • Handles all order states correctly
  • Implements idempotency
  • Logs webhook deliveries
  • Handles errors gracefully

Troubleshooting

Webhook Not Received

  1. Check URL: Verify webhook URL is correct and accessible
  2. Check HTTPS: Ensure endpoint uses HTTPS
  3. Check Firewall: Verify firewall allows HashNut IPs
  4. Check Logs: Review HashNut dashboard for delivery logs

Webhook Retries

If webhooks are being retried:

  1. Check Response: Ensure returning 200 OK with "success"
  2. Check Timeout: Ensure processing completes within 30 seconds
  3. Check Errors: Review error logs for processing failures

Next Steps


Ready to set up webhooks? Check out Quick Start →