Webhooks
Webhooks allow Finternet to notify your application of payment events in real-time.
Overview
Instead of polling the API for payment status updates, webhooks send HTTP POST requests to your server when events occur.
Supported Events
Payment Intent Events
payment_intent.created- Payment intent createdpayment_intent.status_changed- Payment status changedpayment_intent.blockchain_tx_submitted- Transaction submitted to blockchainpayment_intent.blockchain_tx_confirmed- Transaction confirmed (5+ confirmations)payment_intent.settlement_initiated- Settlement process startedpayment_intent.settlement_completed- Settlement completed
Escrow Events
escrow_order.created- Escrow order createddelivery_proof.submitted- Delivery proof submitteddispute.raised- Dispute raiseddispute.resolved- Dispute resolvedtime_lock.released- Time lock expired and funds releasedmilestone.completed- Milestone marked as completed
Webhook Payload
All webhooks follow this structure:
{
"id": "evt_2xYz9AbC123",
"object": "event",
"type": "payment_intent.status_changed",
"data": {
"object": {
"id": "intent_2xYz9AbC123",
"object": "payment_intent",
"status": "SUCCEEDED",
// ... full payment intent object
}
},
"created": 1704067200
}
Setting Up Webhooks
1. Create Webhook Endpoint
Create an HTTP endpoint that accepts POST requests:
// Express.js example
app.post('/webhooks/finternet', express.raw({ type: 'application/json' }), (req, res) => {
const event = JSON.parse(req.body);
// Verify webhook signature
const signature = req.headers['finternet-signature'];
if (!verifySignature(req.body, signature)) {
return res.status(400).send('Invalid signature');
}
// Handle event
handleWebhookEvent(event);
res.json({ received: true });
});
2. Verify Webhook Signature
Always verify webhook signatures to ensure requests are from Finternet:
import crypto from 'crypto';
function verifySignature(payload: string, signature: string): boolean {
const secret = process.env.FINTERNET_WEBHOOK_SECRET;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
3. Handle Events
Process events based on type:
function handleWebhookEvent(event: WebhookEvent) {
switch (event.type) {
case 'payment_intent.status_changed':
handleStatusChange(event.data.object);
break;
case 'payment_intent.settlement_completed':
handleSettlementComplete(event.data.object);
break;
case 'delivery_proof.submitted':
handleDeliveryProof(event.data.object);
break;
// ... handle other events
}
}
function handleStatusChange(paymentIntent: PaymentIntent) {
if (paymentIntent.status === 'SUCCEEDED') {
// Payment confirmed, update order status
updateOrderStatus(paymentIntent.metadata.orderId, 'paid');
}
}
Event Types
payment_intent.status_changed
Triggered when payment intent status changes.
{
"type": "payment_intent.status_changed",
"data": {
"object": {
"id": "intent_2xYz9AbC123",
"status": "SUCCEEDED",
"previous_status": "PROCESSING"
}
}
}
payment_intent.settlement_completed
Triggered when settlement is completed.
{
"type": "payment_intent.settlement_completed",
"data": {
"object": {
"id": "intent_2xYz9AbC123",
"status": "SETTLED",
"settlementStatus": "COMPLETED"
}
}
}
delivery_proof.submitted
Triggered when delivery proof is submitted.
{
"type": "delivery_proof.submitted",
"data": {
"object": {
"id": "delivery_proof_xyz789",
"conditionalPaymentId": "conditional_payment_abc123",
"proofHash": "0xabcdef..."
}
}
}
Best Practices
Idempotency
Handle duplicate events gracefully:
const processedEvents = new Set<string>();
function handleWebhookEvent(event: WebhookEvent) {
if (processedEvents.has(event.id)) {
console.log('Event already processed:', event.id);
return;
}
// Process event
processEvent(event);
// Mark as processed
processedEvents.add(event.id);
}
Retry Logic
Webhooks are retried if your endpoint returns non-2xx status:
- Retry after 1 minute
- Retry after 5 minutes
- Retry after 30 minutes
- Retry after 2 hours
- Retry after 6 hours
Always return 200 OK immediately, then process asynchronously:
app.post('/webhooks/finternet', (req, res) => {
// Return immediately
res.json({ received: true });
// Process asynchronously
processWebhookAsync(req.body);
});
Security
- Always verify webhook signatures
- Use HTTPS for webhook endpoints
- Validate event structure
- Don't trust event data blindly
Testing Webhooks
Use Finternet CLI or dashboard to send test webhooks:
finternet webhooks trigger payment_intent.status_changed \
--payment-intent intent_2xYz9AbC123