Skip to main content

Webhooks

Receive real-time notifications when events happen in your ZendFi account. Webhooks let you automate workflows, update your systems, and provide instant feedback to users.

How Webhooks Work

Payment Event → ZendFi → POST to Your URL → Your Server Processes
  1. An event occurs (payment completed, subscription renewed, etc.)
  2. ZendFi sends an HTTP POST request to your webhook URL
  3. Your server receives and processes the event
  4. Respond with 200 OK to acknowledge receipt

Quick Setup

1. Create a Webhook Endpoint

Build an endpoint on your server to receive webhook events:

// Express.js example
import express from 'express';
import crypto from 'crypto';

const app = express();
app.use(express.json());

app.post('/webhooks/zendfi', (req, res) => {
// Verify the webhook signature
const signature = req.headers['x-zendfi-signature'];
const payload = JSON.stringify(req.body);

const expectedSignature = crypto
.createHmac('sha256', process.env.ZENDFI_WEBHOOK_SECRET)
.update(payload)
.digest('hex');

if (signature !== expectedSignature) {
return res.status(401).json({ error: 'Invalid signature' });
}

// Process the event
const { event, data } = req.body;

switch (event) {
case 'payment.completed':
// Grant access, send confirmation, update database
console.log('Payment completed:', data.payment_id);
break;
case 'subscription.renewed':
// Extend subscription period
console.log('Subscription renewed:', data.subscription.id);
break;
// ... handle other events
}

// Acknowledge receipt
res.status(200).json({ received: true });
});

app.listen(3000);

2. Register Your Webhook

curl -X POST https://api.zendfi.tech/api/v1/webhooks \
-H "Authorization: Bearer zfi_live_abc123..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://myapp.com/webhooks/zendfi",
"events": ["payment.completed", "subscription.renewed"],
"description": "Production webhook"
}'

Response:

{
"id": "wh_abc123def456",
"url": "https://myapp.com/webhooks/zendfi",
"events": ["payment.completed", "subscription.renewed"],
"secret": "whsec_abc123xyz789...",
"active": true,
"created_at": "2025-10-26T12:00:00Z"
}
Save Your Secret!

The webhook secret is only shown once. Save it securely - you'll need it to verify webhook signatures.

3. Start Receiving Events

Once registered, ZendFi will send events to your URL immediately when they occur.


Webhook Events

Payment Events

EventDescriptionTrigger
payment.createdPayment request createdCreate payment API called
payment.pendingAwaiting blockchain confirmationTransaction submitted
payment.completedPayment confirmed on-chainTransaction confirmed
payment.failedPayment failedTransaction failed or expired
payment.expiredPayment link expiredExpiration time reached

Subscription Events

EventDescriptionTrigger
subscription.createdNew subscription startedCustomer subscribed
subscription.renewedSubscription billing completedRenewal payment confirmed
subscription.payment_failedRenewal payment failedPayment not received
subscription.cancelledSubscription cancelledCancel API called
subscription.expiredSubscription expiredMax cycles reached

Installment Events

EventDescriptionTrigger
installment.createdInstallment plan createdCreate installment API called
installment.first_paymentDown payment receivedFirst payment confirmed
installment.payment_dueInstallment payment dueDue date reached
installment.payment_receivedInstallment paidPayment confirmed
installment.payment_latePayment overdueGrace period ended
installment.completedAll installments paidFinal payment confirmed
installment.defaultedCustomer defaultedExcessive late payments

Invoice Events

EventDescriptionTrigger
invoice.createdInvoice createdCreate invoice API called
invoice.sentInvoice emailedSend API called
invoice.viewedCustomer viewed invoiceInvoice page loaded
invoice.payment_receivedPayment receivedFull or partial payment
invoice.paidInvoice fully paidBalance reaches zero
invoice.overdueInvoice past dueDue date passed
invoice.voidedInvoice voidedVoid API called
EventDescriptionTrigger
payment_link.createdPayment link createdCreate link API called
payment_link.payment_completedPayment via linkLink payment confirmed
payment_link.deactivatedLink deactivatedDeactivate API called
payment_link.expiredLink expiredExpiration reached
payment_link.limit_reachedUsage limit hitmax_uses reached

Webhook Payload Structure

All webhooks follow this structure:

{
"id": "evt_abc123def456",
"event": "payment.completed",
"timestamp": "2025-10-26T15:30:00Z",
"api_version": "2025-01",
"data": {
// Event-specific data
}
}

Payment Completed Example

{
"id": "evt_abc123def456",
"event": "payment.completed",
"timestamp": "2025-10-26T15:30:00Z",
"api_version": "2025-01",
"data": {
"payment_id": "pay_xyz789",
"merchant_id": "merchant_abc123",
"amount": 99.99,
"currency": "USD",
"description": "Pro Plan Subscription",
"status": "completed",
"customer_wallet": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
"customer_email": "customer@example.com",
"transaction_signature": "5K2Nz7J8H2...",
"confirmed_at": "2025-10-26T15:29:55Z",
"metadata": {
"user_id": "user_12345",
"plan": "pro"
},
"splits": [
{
"wallet": "PartnerWallet...",
"percentage": 10,
"amount": 9.99,
"settled": true
}
]
}
}

Verifying Webhooks

Always Verify Signatures

Never process webhooks without verifying the signature. Attackers could send fake events to your endpoint.

Signature Verification

ZendFi signs all webhooks with your webhook secret using HMAC-SHA256.

Headers sent with each webhook:

HeaderDescription
X-ZendFi-SignatureHMAC-SHA256 signature
X-ZendFi-TimestampUnix timestamp of the request
X-ZendFi-Event-IDUnique event ID

Verification Code Examples

Node.js:

import crypto from 'crypto';

function verifyWebhook(payload: string, signature: string, secret: string): boolean {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');

return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}

// Usage
const isValid = verifyWebhook(
JSON.stringify(req.body),
req.headers['x-zendfi-signature'],
process.env.ZENDFI_WEBHOOK_SECRET
);

Python:

import hmac
import hashlib

def verify_webhook(payload: str, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()

return hmac.compare_digest(signature, expected)

# Usage
is_valid = verify_webhook(
request.get_data(as_text=True),
request.headers.get('X-ZendFi-Signature'),
os.environ['ZENDFI_WEBHOOK_SECRET']
)

Managing Webhooks

List Webhooks

curl -X GET https://api.zendfi.tech/api/v1/webhooks \
-H "Authorization: Bearer zfi_live_abc123..."

Update Webhook

curl -X PATCH https://api.zendfi.tech/api/v1/webhooks/wh_abc123def456 \
-H "Authorization: Bearer zfi_live_abc123..." \
-H "Content-Type: application/json" \
-d '{
"events": ["payment.completed", "payment.failed"],
"url": "https://myapp.com/webhooks/v2/zendfi"
}'

Delete Webhook

curl -X DELETE https://api.zendfi.tech/api/v1/webhooks/wh_abc123def456 \
-H "Authorization: Bearer zfi_live_abc123..."

Rotate Webhook Secret

curl -X POST https://api.zendfi.tech/api/v1/webhooks/wh_abc123def456/rotate-secret \
-H "Authorization: Bearer zfi_live_abc123..."

Testing Webhooks

Send Test Event

Trigger a test webhook to verify your endpoint:

curl -X POST https://api.zendfi.tech/api/v1/webhooks/wh_abc123def456/test \
-H "Authorization: Bearer zfi_live_abc123..." \
-H "Content-Type: application/json" \
-d '{
"event": "payment.completed"
}'

Using ngrok for Local Development

  1. Install ngrok: npm install -g ngrok
  2. Start your local server: npm run dev
  3. Expose it: ngrok http 3000
  4. Use the ngrok URL as your webhook URL
curl -X POST https://api.zendfi.tech/api/v1/webhooks \
-H "Authorization: Bearer zfi_test_abc123..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://abc123.ngrok.io/webhooks/zendfi",
"events": ["*"]
}'

Retry Policy

If your endpoint doesn't respond with 2xx status, ZendFi retries:

AttemptDelay
1Immediate
25 minutes
330 minutes
42 hours
58 hours
624 hours

After 6 failed attempts, the webhook is marked as failed. You can view failed webhooks in your dashboard.

Retry Headers

Retry attempts include additional headers:

HeaderDescription
X-ZendFi-Retry-CountNumber of retry attempt (1-6)
X-ZendFi-Original-TimestampWhen the event originally occurred

Best Practices

Endpoint Design

  1. Respond Quickly - Return 200 immediately, process async
  2. Idempotency - Handle duplicate events gracefully
  3. Logging - Log all received webhooks for debugging
  4. Error Handling - Catch exceptions, don't crash on bad data

Idempotent Processing

Use the event ID to prevent processing duplicates:

app.post('/webhooks/zendfi', async (req, res) => {
const eventId = req.body.id;

// Check if already processed
const existing = await db.webhookEvents.findOne({ eventId });
if (existing) {
return res.status(200).json({ received: true, duplicate: true });
}

// Mark as processing
await db.webhookEvents.create({ eventId, status: 'processing' });

// Acknowledge immediately
res.status(200).json({ received: true });

// Process asynchronously
processWebhookAsync(req.body);
});

Security

  1. Verify Signatures - Always verify HMAC signatures
  2. Use HTTPS - Only use HTTPS webhook URLs
  3. Validate Data - Don't trust webhook data blindly
  4. IP Allowlist - Optionally restrict to ZendFi IPs

ZendFi webhook IPs (for allowlisting):

  • 34.102.136.180
  • 34.102.136.181
  • 34.102.136.182

Webhook Logs

View recent webhook deliveries in your dashboard or via API:

curl -X GET https://api.zendfi.tech/api/v1/webhooks/wh_abc123def456/logs \
-H "Authorization: Bearer zfi_live_abc123..."

Response:

{
"logs": [
{
"id": "log_xyz789",
"event_id": "evt_abc123",
"event": "payment.completed",
"url": "https://myapp.com/webhooks/zendfi",
"status_code": 200,
"response_time_ms": 145,
"delivered_at": "2025-10-26T15:30:01Z",
"success": true
},
{
"id": "log_xyz790",
"event_id": "evt_def456",
"event": "payment.failed",
"url": "https://myapp.com/webhooks/zendfi",
"status_code": 500,
"response_time_ms": 3000,
"delivered_at": "2025-10-26T15:25:00Z",
"success": false,
"retry_count": 2,
"next_retry": "2025-10-26T17:25:00Z"
}
]
}

Next Steps

  • Payments API - Create payments that trigger webhooks
  • Subscriptions - Set up recurring billing with webhook events
  • SDKs - Use our SDKs for easier webhook handling
Ask AI about the docs...