Webhooks
Webhooks allow you to receive real-time notifications about payment events. Instead of polling the API for status updates, One2Pays will send HTTP POST requests to your webhook endpoint when events occur.
How Webhooks Work
- Register your webhook endpoint with One2Pays
- One2Pays sends events to your endpoint when payments change status
- Verify the signature to ensure the request is from One2Pays
- Process the event and update your system accordingly
Webhook Events
One2Pays sends webhooks for the following events:
Payment Events
payment.created- A new payment was createdpayment.processing- Payment is being processedpayment.succeeded- Payment completed successfullypayment.failed- Payment failedpayment.canceled- Payment was canceledpayment.expired- Payment expired
Withdraw Events
withdraw.paid- A withdraw was successfully completedwithdraw.failed- Withdraw failedwithdraw.canceled- Withdraw was canceled
Registering a Webhook Endpoint
Webhook endpoints are configured through your Merchant Dashboard:
- Go to your One2Pays Dashboard
- Navigate to Settings → Webhooks (or Integrations → Webhooks)
- Configure your webhook endpoint URL (must be HTTPS)
- Select the events you want to receive
- Save your webhook configuration
Note
Webhook configuration is managed through the dashboard, not via API endpoints. The API provides provider webhook endpoints for payment providers, not merchant webhook registration.
Webhook Payload
Webhook requests include the following payload:
{
"id": "evt_1234567890abcdef",
"type": "payment.succeeded",
"data": {
"id": "pay_1234567890abcdef",
"amount": "1000.00",
"currency": "THB",
"status": "succeeded",
"referenceId": "order-12345"
},
"createdAt": "2024-01-01T00:00:00Z"
}Verifying Webhook Signatures
Always verify webhook signatures to ensure requests are from One2Pays:
import crypto from 'crypto';
function verifyWebhookSignature(
payload: string,
signature: string,
timestamp: string,
secret: string
): boolean {
// Extract signature hash (format: sha256=<hash>)
const signatureHash = signature.replace(/^sha256=/, '');
// Parse timestamp (in milliseconds)
const timestampMs = parseInt(timestamp, 10);
if (isNaN(timestampMs) || timestampMs <= 0) {
return false;
}
// Create signed payload: timestamp.payload
const signedPayload = `${timestampMs}.${payload}`;
// Compute expected signature
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
// Compare signatures using timing-safe comparison
return crypto.timingSafeEqual(
Buffer.from(signatureHash, 'hex'),
Buffer.from(expectedSignature, 'hex')
);
}
// Usage in webhook handler
export async function POST(request: Request) {
const payload = await request.text();
const signature = request.headers.get('x-webhook-signature');
const timestamp = request.headers.get('x-webhook-timestamp');
if (!signature || !timestamp || !verifyWebhookSignature(payload, signature, timestamp, WEBHOOK_SECRET)) {
return new Response('Invalid signature', { status: 401 });
}
const event = JSON.parse(payload);
// Process event...
return new Response('OK', { status: 200 });
}Webhook Headers
One2Pays includes these headers in webhook requests:
| Header | Description |
|---|---|
X-Webhook-Signature | HMAC signature of the payload (format: sha256=<hex_signature>) |
X-Webhook-Timestamp | Timestamp in milliseconds used for signature |
X-Webhook-Event | Event type (e.g., payment.succeeded) |
X-Webhook-Id | Unique event ID |
Content-Type | application/json |
Handling Webhooks
Best Practices
- Verify signatures - Always verify webhook signatures
- Idempotency - Handle duplicate events gracefully using event IDs
- Respond quickly - Return 200 OK within 5 seconds
- Log events - Log all webhook events for debugging
- Handle failures - Implement retry logic for failed webhook processing
Example: Complete Webhook Handler
import crypto from 'crypto';
const WEBHOOK_SECRET = process.env.PAYMENT_WEBHOOK_SECRET!;
export async function POST(request: Request) {
try {
const payload = await request.text();
const signature = request.headers.get('x-webhook-signature');
const eventId = request.headers.get('x-webhook-event-id');
// Verify signature
if (!signature || !verifyWebhookSignature(payload, signature, WEBHOOK_SECRET)) {
return new Response('Invalid signature', { status: 401 });
}
const event = JSON.parse(payload);
// Handle idempotency (check if event already processed)
if (await isEventProcessed(eventId)) {
return new Response('OK', { status: 200 });
}
// Process event
switch (event.type) {
case 'payment.succeeded':
await handlePaymentSucceeded(event.data);
break;
case 'payment.failed':
await handlePaymentFailed(event.data);
break;
case 'withdraw.paid':
await handleWithdrawPaid(event.data);
break;
case 'withdraw.failed':
await handleWithdrawFailed(event.data);
break;
default:
console.log('Unhandled event type:', event.type);
}
// Mark event as processed
await markEventProcessed(eventId);
return new Response('OK', { status: 200 });
} catch (error) {
console.error('Webhook error:', error);
return new Response('Error', { status: 500 });
}
}
async function handlePaymentSucceeded(payment: any) {
// Update order status, send confirmation email, etc.
console.log('Payment succeeded:', payment.id);
}
async function handlePaymentFailed(payment: any) {
console.log('Payment failed:', payment.id);
// Notify customer, update order status, etc.
}
async function handlePaymentCanceled(payment: any) {
console.log('Payment canceled:', payment.id);
// Update order status, etc.
}
async function handleWithdrawPaid(withdraw: any) {
console.log('Withdraw completed:', withdraw.id);
// Update your system
}
async function handleWithdrawFailed(withdraw: any) {
console.log('Withdraw failed:', withdraw.id);
// Handle failure
}Webhook Retries
One2Pays automatically retries failed webhook deliveries:
- Initial attempt: Immediate
- Retry 1: After 1 minute
- Retry 2: After 5 minutes
- Retry 3: After 15 minutes
- Retry 4: After 1 hour
- Retry 5: After 6 hours
- Retry 6: After 24 hours
Webhooks are considered failed if:
- Your endpoint returns a non-2xx status code
- Your endpoint times out (>30 seconds)
- Network error occurs
Testing Webhooks
You can test webhooks using the test webhook feature in your merchant dashboard, or by using a tool like ngrok for local development.
Security Best Practices
Always verify webhook signatures
Never process webhooks without signature verification.
- Use HTTPS - Always use HTTPS for webhook endpoints
- Verify signatures - Always verify HMAC signatures
- Check event IDs - Implement idempotency using event IDs
- Validate payloads - Validate webhook payload structure
- Rate limiting - Implement rate limiting on webhook endpoints
- Logging - Log all webhook events for audit trails