Skip to main content

Create a Webhook

Overview

Create a Webhook to receive automated notifications about events in your Ahrvo Network account. Webhooks enable real-time integrations by sending HTTP POST requests to your server when events occur.

Resource Access

  • User Permissions: Admin users only
  • Endpoint: POST /webhooks

Arguments

ParameterTypeRequiredDescription
urlstringYesHTTPS endpoint to receive webhook events
enabled_eventsarrayNoSpecific events to subscribe to (default: all events)
authenticationobjectNoAuthentication credentials for your webhook endpoint

Example Request (Basic Setup)

curl -X POST \
'https://api.ahrvo.network/payments/na/webhooks' \
-u username:password \
-H 'Content-Type: application/json' \
-d '{
"url": "https://api.yourcompany.com/webhooks/finix"
}'

Example Request (With Specific Events)

curl -X POST \
'https://api.ahrvo.network/payments/na/webhooks' \
-u username:password \
-H 'Content-Type: application/json' \
-d '{
"url": "https://api.yourcompany.com/webhooks/finix",
"enabled_events": [
{
"entity": "transfer",
"types": ["succeeded", "failed"]
},
{
"entity": "merchant",
"types": ["created", "verification.succeeded", "verification.failed"]
},
{
"entity": "payment_instrument",
"types": ["created", "verification.succeeded"]
}
]
}'

Example Request (With Bearer Authentication)

curl -X POST \
'https://api.ahrvo.network/payments/na/webhooks' \
-u username:password \
-H 'Content-Type: application/json' \
-d '{
"url": "https://api.yourcompany.com/webhooks/finix",
"authentication": {
"type": "BEARER",
"bearer": {
"token": "your-secret-bearer-token-here"
}
},
"enabled_events": [
{
"entity": "transfer",
"types": ["succeeded", "failed"]
}
]
}'

Example Request (With Basic Authentication)

curl -X POST \
'https://api.ahrvo.network/payments/na/webhooks' \
-u username:password \
-H 'Content-Type: application/json' \
-d '{
"url": "https://api.yourcompany.com/webhooks/finix",
"authentication": {
"type": "BASIC",
"basic": {
"username": "webhook_user",
"password": "secure_webhook_password"
}
}
}'

Example Response

{
"id": "WHwebhook123",
"created_at": "2023-12-10T20:00:00Z",
"updated_at": "2023-12-10T20:00:00Z",
"url": "https://api.yourcompany.com/webhooks/finix",
"enabled": true,
"authentication": {
"type": "BEARER",
"bearer": {
"token": "your-secret-bearer-token-here"
}
},
"enabled_events": [
{
"entity": "transfer",
"types": ["succeeded", "failed"]
}
],
"secret_signing_key": "whsec_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
"_links": {
"self": {
"href": "https://api.ahrvo.network/payments/na/webhooks/WHwebhook123"
}
}
}

Additional Information

  • URL: Your webhook endpoint
    • Must be HTTPS (not HTTP)
    • Publicly accessible
    • Must respond with 200-299 status
    • Timeout: 30 seconds
    • Finix sends POST requests to this URL
  • Enabled Events: Filter which events to receive
    • Omit to receive ALL events (not recommended for production)
    • Specify array of entity/types combinations
    • Reduces noise
    • Improves performance
    • Common entities: transfer, merchant, payment_instrument, settlement
    • Common types: created, succeeded, failed, updated
  • Authentication: Secure your webhook endpoint
    • NONE: No authentication (not recommended)
    • BASIC: HTTP Basic Auth
      • Send username and password
      • Finix includes in Authorization header
    • BEARER: Bearer token
      • Send token
      • Finix includes as Authorization: Bearer {token}
    • Choose based on your endpoint security
  • Secret Signing Key: Verify webhook authenticity
    • Returned ONLY on creation
    • Use to verify webhook signatures
    • HMAC-SHA256 signature
    • Prevents spoofed webhooks
    • Store securely
    • Cannot retrieve later
  • Enabled: Active status
    • Created as enabled: true by default
    • Can disable via PUT /webhooks/{id}
    • Disabled webhooks don't send events
    • Use to pause temporarily
  • Automatic Retry: Failed deliveries
    • Retries up to 3 times
    • Exponential backoff
    • 1st retry: 1 minute
    • 2nd retry: 5 minutes
    • 3rd retry: 15 minutes
    • After 3 failures: Stops sending
    • Must fix endpoint and re-enable
  • Event Format: POST request body
    {
    "type": "transfer.succeeded",
    "id": "EVevent123",
    "created_at": "2023-12-10T20:00:00Z",
    "data": {
    "id": "TRtransfer123",
    "state": "SUCCEEDED",
    "amount": 10000,
    // ... full resource object
    }
    }

Available Events

Transfer Events

  • transfer.created: Transfer created
  • transfer.succeeded: Transfer completed successfully
  • transfer.failed: Transfer failed
  • transfer.updated: Transfer metadata updated

Merchant Events

  • merchant.created: Merchant created
  • merchant.updated: Merchant info updated
  • merchant.verification.succeeded: Merchant verified
  • merchant.verification.failed: Merchant verification failed

Payment Instrument Events

  • payment_instrument.created: Card/bank added
  • payment_instrument.updated: Card/bank updated
  • payment_instrument.verification.succeeded: Card/bank verified
  • payment_instrument.verification.failed: Card/bank verification failed

Settlement Events

  • settlement.created: Settlement batch created
  • settlement.approved: Settlement approved for payout
  • settlement.funding_transfer.created: Payout initiated
  • settlement.funding_transfer.succeeded: Payout completed
  • settlement.funding_transfer.failed: Payout failed

Subscription Events

  • subscription.created: Subscription started
  • subscription.updated: Subscription modified
  • subscription.canceled: Subscription canceled
  • subscription.expired: Subscription ended

Dispute Events

  • dispute.created: Chargeback received
  • dispute.updated: Dispute status changed
  • dispute.won: Dispute won
  • dispute.lost: Dispute lost

Use Cases

Production Payment Notifications

# Subscribe to critical payment events
curl -X POST \
'https://api.ahrvo.network/payments/na/webhooks' \
-u username:password \
-H 'Content-Type: application/json' \
-d '{
"url": "https://api.yourcompany.com/webhooks/payments",
"authentication": {
"type": "BEARER",
"bearer": {
"token": "prod_webhook_secret_token_xyz"
}
},
"enabled_events": [
{
"entity": "transfer",
"types": ["succeeded", "failed"]
}
]
}'
  • Get notified when payments complete
  • No polling required
  • Real-time order fulfillment
  • Immediate failure handling

Merchant Onboarding Webhook

# Monitor merchant verification
curl -X POST \
'https://api.ahrvo.network/payments/na/webhooks' \
-u username:password \
-H 'Content-Type: application/json' \
-d '{
"url": "https://api.yourcompany.com/webhooks/merchants",
"enabled_events": [
{
"entity": "merchant",
"types": ["verification.succeeded", "verification.failed"]
}
]
}'
  • Know when merchant is verified
  • Trigger welcome emails
  • Enable merchant dashboard access
  • Handle verification failures

Settlement Notifications

# Track payouts to merchants
curl -X POST \
'https://api.ahrvo.network/payments/na/webhooks' \
-u username:password \
-H 'Content-Type: application/json' \
-d '{
"url": "https://api.yourcompany.com/webhooks/settlements",
"enabled_events": [
{
"entity": "settlement",
"types": ["funding_transfer.succeeded", "funding_transfer.failed"]
}
]
}'
  • Notify merchants of payouts
  • Update accounting systems
  • Handle failed payouts
  • Reconciliation automation

Development/Testing Webhook

# Test webhook with all events
curl -X POST \
'https://api.ahrvo.network/payments/na/webhooks' \
-u username:password \
-H 'Content-Type: application/json' \
-d '{
"url": "https://webhook.site/your-unique-url",
"enabled_events": []
}'
  • Use webhook.site or similar for testing
  • Receive all events to understand structure
  • Debug webhook handling
  • Validate event data

Best Practices

  • Use HTTPS: Required for security

    • Not HTTP
    • Valid SSL certificate
    • No self-signed certs in production
    • Publicly accessible
  • Store Signing Key: Save immediately

    // On webhook creation
    const webhook = await createWebhook({
    url: 'https://api.yourcompany.com/webhooks'
    });

    // IMPORTANT: Store this securely
    await storeSecurely({
    webhook_id: webhook.id,
    signing_key: webhook.secret_signing_key
    });

    // Cannot retrieve later!
  • Filter Events: Don't subscribe to everything

    • Only events you need
    • Reduces load on your server
    • Easier to process
    • Better performance
    • Example: Only transfer.succeeded for order fulfillment
  • Authenticate Endpoint: Secure your webhook

    • Use BEARER or BASIC authentication
    • Don't expose public webhook
    • Prevents unauthorized access
    • Verify signing key in your handler
  • Idempotency: Handle duplicate events

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

    // Check if already processed
    const exists = await db.events.findOne({ id: eventId });
    if (exists) {
    console.log('Duplicate event, ignoring');
    return res.status(200).send('OK');
    }

    // Process event
    await processEvent(event);

    // Store event ID
    await db.events.insert({ id: eventId, processed_at: new Date() });

    res.status(200).send('OK');
    });
  • Quick Response: Return 200 immediately

    app.post('/webhooks/finix', async (req, res) => {
    const event = req.body;

    // Respond immediately
    res.status(200).send('OK');

    // Process asynchronously
    processWebhookAsync(event).catch(err => {
    console.error('Webhook processing error:', err);
    });
    });
  • Verify Signature: Prevent spoofing

    const crypto = require('crypto');

    function verifyWebhookSignature(payload, signature, secret) {
    const hmac = crypto.createHmac('sha256', secret);
    hmac.update(JSON.stringify(payload));
    const calculated = hmac.digest('hex');

    return calculated === signature;
    }

    app.post('/webhooks/finix', (req, res) => {
    const signature = req.headers['x-finix-signature'];
    const isValid = verifyWebhookSignature(
    req.body,
    signature,
    WEBHOOK_SIGNING_KEY
    );

    if (!isValid) {
    return res.status(401).send('Invalid signature');
    }

    // Process webhook
    res.status(200).send('OK');
    });
  • Error Handling: Log and alert

    • Log all webhook processing errors
    • Alert on repeated failures
    • Monitor webhook delivery success rate
    • Set up dead letter queue for failed events

Common Workflows

Complete Webhook Setup

  1. Create webhook with your URL
  2. Store secret_signing_key securely
  3. Implement webhook handler endpoint
  4. Verify signature in handler
  5. Return 200 OK quickly
  6. Process events asynchronously
  7. Test with test events
  8. Monitor for failures
  9. Deploy to production

Webhook Handler Implementation

const express = require('express');
const crypto = require('crypto');
const app = express();

app.use(express.json());

// Webhook endpoint
app.post('/webhooks/finix', async (req, res) => {
try {
// 1. Verify signature
const signature = req.headers['x-finix-signature'];
if (!verifySignature(req.body, signature)) {
return res.status(401).send('Invalid signature');
}

// 2. Check idempotency
const eventId = req.body.id;
if (await isProcessed(eventId)) {
return res.status(200).send('OK');
}

// 3. Respond immediately
res.status(200).send('OK');

// 4. Process asynchronously
const event = req.body;

switch (event.type) {
case 'transfer.succeeded':
await handleTransferSucceeded(event.data);
break;
case 'transfer.failed':
await handleTransferFailed(event.data);
break;
case 'merchant.verification.succeeded':
await handleMerchantVerified(event.data);
break;
default:
console.log('Unhandled event type:', event.type);
}

// 5. Mark as processed
await markProcessed(eventId);

} catch (error) {
console.error('Webhook error:', error);
// Already sent 200, log error for investigation
}
});

async function handleTransferSucceeded(transfer) {
// Fulfill order
await fulfillOrder(transfer.tags.order_id);

// Send confirmation email
await sendConfirmation(transfer.tags.customer_email);

// Update database
await db.orders.update(
{ id: transfer.tags.order_id },
{ status: 'paid', payment_id: transfer.id }
);
}

app.listen(3000);

Testing Webhooks Locally

# Use ngrok to expose local server
ngrok http 3000

# Create webhook with ngrok URL
curl -X POST \
'https://api.ahrvo.network/payments/na/webhooks' \
-u username:password \
-H 'Content-Type: application/json' \
-d '{
"url": "https://abc123.ngrok.io/webhooks/finix"
}'

# Trigger events in test environment
# Watch webhook calls in real-time

Security Considerations

  • HTTPS Only: Never use HTTP

    • SSL/TLS encryption required
    • Valid certificate
    • No self-signed in production
  • Signature Verification: Always verify

    • Use secret_signing_key
    • Prevent spoofed webhooks
    • Reject invalid signatures
    • HMAC-SHA256 verification
  • Authentication: Secure your endpoint

    • Bearer token recommended
    • Strong, random tokens
    • Rotate periodically
    • Don't commit to source control
  • Signing Key Storage: Keep secure

    • Store in secrets manager
    • Environment variables
    • Never commit to git
    • Rotate if compromised
  • Rate Limiting: Protect your endpoint

    • Limit requests per minute
    • Prevent abuse
    • Use queue for processing
    • Handle bursts gracefully

Troubleshooting

Webhook Not Receiving Events

  • Check URL is publicly accessible
  • Verify HTTPS with valid cert
  • Ensure endpoint returns 200
  • Check enabled_events filter
  • Verify webhook is enabled
  • Check firewall rules

Events Failing Delivery

  • Check endpoint response time (< 30s)
  • Verify 200 status code
  • Check server logs for errors
  • Test endpoint manually
  • Review authentication

Duplicate Events

  • Implement idempotency
  • Store event IDs
  • Check for processing
  • Ignore duplicates

Missing Signing Key

  • Only shown on creation
  • Cannot retrieve later
  • Create new webhook
  • Update your handler
  • GET /webhooks: List all webhooks
  • GET /webhooks/{id}: Fetch webhook details
  • PUT /webhooks/{id}: Update webhook (enable/disable, change URL)