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 OKwith body"success"for successful processing - ✅ Should respond within 30 seconds
Webhook Events
Payment Status Updates
HashNut sends webhooks when order status changes:
| Event | Trigger | State Value |
|---|---|---|
| Payment Initiated | Customer starts payment | 1 |
| Payment Confirming | Transaction confirmed | 3 |
| Payment Success | Payment verified | 4 |
| Payment Failed | Transaction failed | -1 |
| Order Expired | Order expired | -2 |
| Order Cancelled | Order 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
| Field | Type | Description |
|---|---|---|
payOrderId | string | HashNut platform order ID |
merchantOrderId | string | Merchant order ID |
accessSign | string | Access signature for querying order |
state | integer | Order 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/htmlortext/plain
Failure:
- Status:
400or500 - Body:
"failed"(plain text)
Response Examples
- JavaScript
- Python
- Java
// Express.js
res.status(200).send('success');
// Or for errors
res.status(400).send('failed');
# Flask
return 'success', 200
# Or for errors
return 'failed', 400
// Spring Boot
return ResponseEntity.ok("success");
// Or for errors
return ResponseEntity.status(400).body("failed");
Webhook Processing
Recommended Flow
Processing Steps
- Parse JSON: Parse request body
- Query Order Status: Use
queryPayOrderWithAccessSignto get latest order details - Verify Order: Ensure order details match your records
- Update Database: Update order status in your system
- Process Business Logic: Fulfill order, send notifications, etc.
- Return Success: Return
200 OKwith"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 OKwith body"success"
Retry Sequence
Idempotency
Important: Webhooks may be delivered multiple times. Implement idempotency:
- Check if Already Processed: Use
payOrderIdto check if webhook was already processed - Idempotent Operations: Ensure your processing logic is idempotent
- 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
- Use HTTPS: Webhook endpoints must use HTTPS
- Validate Data: Always query order status to get latest details
- Handle Errors Gracefully: Return appropriate error codes
- Monitor Webhooks: Log all webhook deliveries for monitoring
- Rate Limiting: Implement rate limiting on webhook endpoints
- 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 OKwith"success"for valid webhooks - Handles all order states correctly
- Implements idempotency
- Logs webhook deliveries
- Handles errors gracefully
Troubleshooting
Webhook Not Received
- Check URL: Verify webhook URL is correct and accessible
- Check HTTPS: Ensure endpoint uses HTTPS
- Check Firewall: Verify firewall allows HashNut IPs
- Check Logs: Review HashNut dashboard for delivery logs
Webhook Retries
If webhooks are being retried:
- Check Response: Ensure returning
200 OKwith"success" - Check Timeout: Ensure processing completes within 30 seconds
- Check Errors: Review error logs for processing failures
Next Steps
- Learn about Order States
- Review Error Handling
- Explore API Reference
Ready to set up webhooks? Check out Quick Start →