Why Use Webhooks

When integrating with HIFI's stablecoin infrastructure, it is essential for your application to respond to events in real time. By doing so, your backend systems can promptly take appropriate actions based on events occurring within your HIFI accounts.

To start receiving webhook events, register a webhook endpoint . Once registered, HIFI will push real-time event data to your application's webhook endpoint whenever an event occurs in your HIFI account. Webhook events are delivered via HTTPS as a JSON payload containing an Event object.

Receiving webhook events is particularly valuable for monitoring asynchronous events such as when a transaction is confirmed or a user's KYC status for a rail is updated.

Event Overview

By registering webhook endpoints, you enable HIFI to automatically send Event objects as part of POST requests to the registered webhook endpoint hosted by your application. Once your webhook endpoint receives an Event, your application can execute backend actions, such as updating transaction records or notifying users about the event.

Below is an example of an event you will receive after successfully creating a HIFI user:

{
  "eventId": "evt_1934afd15d318dc8d5",
  "eventCategory": "USER",
  "eventType": "USER.STATUS",
  "eventAction": "CREATE",
  "createdAt": "2024-11-20T19:12:00.549Z",
  "timestamp": "2024-11-20T19:12:01.429Z",
  "data": {
  	"id": "1127a818-68cb-4ac0-a9ae-d17b5fe47b6f",
    "name": "John Doe",
    "type": "individual",
    "email": "[email protected]"
  }
}

Event Category

  • USER: Events related to user objects.
  • KYC: Events related to Know Your Customer (KYC) data or statuses.
  • ACCOUNT: Events associated with user accounts.
  • TRANSFER: Events related to transfers.
  • BRIDGING: Events for bridging assets between networks.

Event Type

  • USER.STATUS: Creation or updates to the user object.
  • KYC.DATA: Updates to the user's KYC data.
  • KYC.STATUS: Submission or updates to the user's KYC status for any rail.
  • ACCOUNT.ONRAMP: Creation or updates of onramp accounts.
  • ACCOUNT.OFFRAMP: Creation or updates of offramp accounts.
  • ACCOUNT.VIRTUAL_ACCOUNT: Creation or updates of virtual accounts.
  • TRANSFER.CRYPTO_TO_CRYPTO: Status updates for crypto-to-crypto transfers.
  • TRANSFER.CRYPTO_TO_FIAT: Status updates for crypto-to-fiat transfers.
  • TRANSFER.FIAT_TO_CRYPTO: Status updates for fiat-to-crypto transfers.
  • BRIDGING: Events related to bridging assets across blockchains.

Event Action:

  • CREATE: Indicates a new object or transaction was created.
  • UPDATE: Indicates an existing object or transaction was updated.

Retry Behavior

HIFI will attempt to re-deliver a given event to your webhook endpoint for up to 24 hours using exponential backoff if it does not receive a 2xx status from the webhook endpoint.

Retry interval: Between 60 seconds and one hour, depending on the number of retries.

If your endpoint is disabled or deleted when HIFI attempts a retry, future retries of that event will be prevented. However, if you disable and then re-enable the webhook endpoint before HIFI retries, you can still expect future retry attempts.

Verify Data Integrity and Ensure Events Are Sent from HIFI

After activating your webhook, you will receive a webhook secret. This secret is used to sign the payload using JWT, and the signature is included in the Authorization header of the request. You must verify this signature to ensure the authenticity of the event. By validating the JWT signature with your webhook secret, you can confirm that the event was sent by HIFI and that the data has not been tampered with during transmission. This verification step is essential for maintaining the security and integrity of your webhook integration.

# Replace \n if needed 
WEBHOOK_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\n...........\n-----END PUBLIC KEY-----\n"
from flask import Flask, request, jsonify
import jwt

app = Flask(__name__)
port = 3000  # Replace with your desired port number
webhook_public_key = '''your_webhook_public_key'''  # Replace with your actual webhook public key

@app.route('/webhook', methods=['POST'])
def webhook():
    auth_header = request.headers.get('Authorization')
    if not auth_header:
        return 'Authorization header is missing', 401

    jwt_token = auth_header.split(' ')[1]

    try:
        decoded = jwt.decode(jwt_token, webhook_public_key, algorithms=['RS256'])
        print('Token is valid. Decoded payload:', decoded)
        # Process the decoded payload as needed
        return '', 200
    except jwt.ExpiredSignatureError:
        print('Token has expired')
        return 'Token has expired', 401
    except jwt.InvalidTokenError as e:
        print('Failed to verify token:', str(e))
        return 'Token verification failed', 401

if __name__ == '__main__':
    app.run(port=port)

const express = require('express');
const bodyParser = require('body-parser');
const jwt = require('jsonwebtoken');

const app = express();
const port = 3000; // Replace with your desired port number
const webhookPublicKey = 'your_webhook_public_key'; // Replace with your actual webhook secret

// Middleware to parse JSON bodies
app.use(bodyParser.json());

// Endpoint to handle incoming webhook POST requests
app.post('/webhook', (req, res) => {
    const authHeader = req.headers['authorization'];
    if (!authHeader) {
        return res.status(401).send('Authorization header is missing');
    }

    const jwtToken = authHeader.split(' ')[1];

    try {
        jwt.verify(jwtToken, webhookPublicKey.replace(/\\n/g, '\n'), { algorithms: ['RS256'] },(err, decoded) => {
            if (err) {
                console.error('Failed to verify token:', err.message);
                throw new Error("wrong token")
            } else {
                console.log('Token is valid. Decoded payload:', decoded);
            }
        });
        // Process the decoded payload as needed
        res.sendStatus(200);
    } catch (err) {
        console.error('Token verification failed:', err.message);
        res.status(401).send('Token verification failed');
    }
});

// Start the server
app.listen(port, () => {
    console.log(Server is running on http://localhost:${port});
});