Webhooks and Events
Understanding OwnPay's event-driven architecture.
What are Webhooks?
Webhooks are automatic HTTP POST requests that OwnPay sends to your server when events occur.
Instead of polling OwnPay asking "did anything happen?", OwnPay pushes notifications to you automatically.
Benefits
- Real-time - Instant notification
- Efficient - No polling needed
- Reliable - Retry on failure
- Verified - Signature authentication
Event Types
OwnPay fires events for:
Payment Events
payment.created- Payment initiatedpayment.completed- Payment successfulpayment.failed- Payment declinedpayment.refunded- Refund processedpayment.cancelled- Payment cancelledpayment.disputed- Chargeback filed
Customer Events
customer.created- Customer registeredcustomer.updated- Customer data changedcustomer.deleted- Customer removed
Gateway Events
gateway.added- Gateway configuredgateway.updated- Settings changedgateway.removed- Gateway deleted
Settlement Events
settlement.completed- Funds settledsettlement.failed- Settlement failedsettlement.pending- Awaiting settlement
Setting Up Webhooks
Configure Webhook Endpoint
- Go to Developers → Webhooks
- Enter your webhook URL (must be HTTPS)
- Copy the webhook secret
- Click Save
Webhook URL Requirements
- HTTPS only - Must be secure
- Public - Accessible from internet
- Fast - Respond within 30 seconds
- Idempotent - Handle duplicate deliveries
Test Webhook
- Go to Developers → Webhooks
- Click Send Test
- Select event type
- Check your logs for webhook
Webhook Structure
Request Format
http
POST /webhooks/ownpay HTTP/1.1
Host: your-domain.com
Content-Type: application/json
X-OwnPay-Signature: sha256=abcd1234...
X-OwnPay-Timestamp: 1234567890
{
"type": "payment.completed",
"id": "evt_abc123",
"created_at": "2024-01-15T10:30:00Z",
"data": {
"id": "pay_xyz789",
"amount": 5000,
"currency": "USD",
"status": "completed",
"customer_email": "[email protected]",
"brand_id": "brand_123"
}
}Request Headers
Content-Type: application/json- JSON payloadX-OwnPay-Signature- HMAC signatureX-OwnPay-Timestamp- Unix timestampUser-Agent: OwnPay/1.0- Source identifier
Signature Verification
Critical: Always verify webhook signatures to ensure authenticity.
Verification Process
- Get the signature from
X-OwnPay-Signatureheader - Create signature string:
{timestamp}.{payload} - Compute HMAC-SHA256 using your webhook secret
- Compare computed vs received signature
Example (PHP)
php
$signature = $_SERVER['HTTP_X_OWNPAY_SIGNATURE'] ?? '';
$timestamp = $_SERVER['HTTP_X_OWNPAY_TIMESTAMP'] ?? '';
$payload = file_get_contents('php://input');
$secret = 'whsec_your_webhook_secret';
$expected = hash_hmac('sha256', "$timestamp.$payload", $secret);
if (!hash_equals($expected, $signature)) {
die('Invalid signature');
}Example (Node.js)
javascript
const crypto = require('crypto');
const signature = req.headers['x-ownpay-signature'];
const timestamp = req.headers['x-ownpay-timestamp'];
const payload = req.body;
const secret = 'whsec_your_webhook_secret';
const computed = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${JSON.stringify(payload)}`)
.digest('hex');
if (computed !== signature) {
return res.status(401).send('Invalid signature');
}Handling Webhooks
Best Practices
- Verify signature - Authentication
- Check event type - Route to handler
- Process idempotently - Handle duplicates
- Respond quickly - Don't slow OwnPay
- Queue for async - Process in background
- Log everything - For debugging
- Return 2xx - Tell OwnPay success
Idempotent Processing
Webhooks may be delivered multiple times:
php
// Check if already processed
$existing = Payment::where('event_id', $event['id'])->first();
if ($existing) {
return response()->json(['status' => 'already processed']);
}
// Process the event
$payment = Payment::create([
'event_id' => $event['id'],
'amount' => $event['data']['amount'],
...
]);
return response()->json(['status' => 'processed']);Async Processing
Don't process webhooks synchronously:
php
// Bad: Slow response
$this->sendEmail($customer);
$this->updateDatabase($payment);
return 'OK';
// Good: Fast response, async processing
Queue::push(new ProcessPayment($event));
return 'OK';Webhook Delivery
Retry Logic
If your server doesn't respond with 2xx:
- Retry 1: After 30 seconds
- Retry 2: After 5 minutes
- Retry 3: After 30 minutes
- Retry 4: After 2 hours
- Retry 5: After 8 hours
- Retry 6: After 24 hours
After 6 failures, webhook is abandoned.
Monitoring
- Go to Developers → Webhooks
- View webhook history
- See delivery status:
- ✅ Delivered - Success
- ⏳ Pending - Awaiting retry
- ❌ Failed - All retries exhausted
Manual Retry
- Go to Developers → Webhooks
- Find failed webhook
- Click Retry
- OwnPay resends immediately
Common Patterns
Update Order on Payment
javascript
app.post('/webhooks/ownpay', (req, res) => {
const event = req.body;
if (event.type === 'payment.completed') {
const orderId = event.data.metadata.order_id;
// Update order status
Order.updateOne(
{ id: orderId },
{ status: 'paid', paid_at: new Date() }
);
// Send confirmation email
sendConfirmationEmail(event.data.customer_email);
}
res.json({ received: true });
});Handle Refunds
javascript
if (event.type === 'payment.refunded') {
const payment = event.data;
// Update order
Order.updateOne(
{ payment_id: payment.id },
{ status: 'refunded', refund_date: new Date() }
);
// Restore inventory
restoreInventory(payment.metadata.items);
// Notify customer
sendRefundNotification(payment.customer_email);
}Log All Events
javascript
if (event.type.includes('payment')) {
logger.info('Payment event', {
type: event.type,
payment_id: event.data.id,
amount: event.data.amount,
status: event.data.status,
timestamp: event.created_at
});
}Troubleshooting
Webhook Not Received
Check:
- Is webhook URL registered?
- Is webhook secret correct?
- Is endpoint accessible from internet?
- Do firewall rules allow inbound?
- Check webhook history for errors
Webhook Signature Invalid
Check:
- Is webhook secret correct?
- Is timestamp recent (< 5 minutes)?
- Are you parsing raw body correctly?
- Are you using correct hash algorithm?
Missing Webhooks
Check:
- Are events enabled?
- Check webhook history
- Do payment events exist in logs?
- Try manual retry to test endpoint
Testing Webhooks
Using Webhook.cool
- Generate temporary webhook URL
- Enter in OwnPay webhook settings
- Send test webhook
- Inspect webhook in Webhook.cool
Local Testing with ngrok
bash
# Start ngrok
ngrok http 3000
# Use the ngrok URL in OwnPay webhooks
# https://abc123.ngrok.io/webhooks/ownpay
# View requests in ngrok dashboardDevelopment Patterns
- Use test mode for development
- Use webhook.cool or ngrok for local
- Use staging environment for pre-live
- Use actual endpoint for production
Summary
Webhooks provide:
- ✅ Real-time notifications
- ✅ Automatic delivery with retries
- ✅ Verified with signatures
- ✅ Flexible event types
Ready to set up webhooks? → Webhooks Setup