> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hifi.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Events & Webhooks

> Webhooks deliver real-time event notifications from HIFI to your application. When events occur in your HIFI account, HIFI sends HTTP POST requests to your registered webhook endpoints, enabling your systems to respond immediately.

## How Webhooks Work

<Steps>
  <Step title="Register endpoint">
    Configure a webhook endpoint URL in the HIFI Dashboard.
  </Step>

  <Step title="Receive events">
    HIFI sends POST requests to your endpoint when events occur.
  </Step>

  <Step title="Verify signature">
    Validate the JWT signature using your webhook secret to ensure authenticity.
  </Step>

  <Step title="Process event">
    Execute your application logic based on the event data.
  </Step>

  <Step title="Return 2xx response">
    Respond with a 2xx status code to acknowledge receipt.
  </Step>
</Steps>

## Registering Webhooks

Register webhook endpoints in the HIFI Dashboard:

1. Navigate to [**Developer → Webhooks**](https://app.hifi.com/developer/webhooks)
2. Enter your webhook URL (must be HTTPS)
3. Save to receive your webhook secret

<Info>
  **Webhook Secret:** After registration, you'll receive a public key used to
  verify webhook signatures. Store this securely - you'll need it to validate
  incoming requests.
</Info>

## Event Structure

All webhook events follow a consistent structure:

```json theme={null}
{
  "eventId": "evt_1957117404034e3ade",
  "eventCategory": "USER",
  "eventType": "USER.CREATE",
  "eventAction": "CREATE",
  "data": {
    "id": "usr_abc123",
    "email": "user@example.com",
    "type": "individual",
    "wallets": {
      "INDIVIDUAL": {
        "POLYGON": {
          "address": "0xa8A642FBA80749318036C97344fC73aE0B64c608"
        }
      }
    }
  },
  "createdAt": "2025-03-07T14:51:44.099Z",
  "timestamp": "2025-03-07T14:52:00.375Z",
  "version": "v2"
}
```

**Event Fields:**

<ResponseField name="eventId" type="string">
  Unique identifier for this webhook event.
</ResponseField>

<ResponseField name="eventCategory" type="string">
  Broad category describing the domain of the event (e.g., USER, WALLET, ONRAMP,
  OFFRAMP).
</ResponseField>

<ResponseField name="eventType" type="string">
  Specific event type or trigger (such as USER.CREATE, WALLET.TRANSFER.UPDATE).
</ResponseField>

<ResponseField name="eventAction" type="string">
  Operation performed (CREATE, UPDATE, DELETE).
</ResponseField>

<ResponseField name="data" type="object">
  Object containing details and payload for this event (structure varies by
  event type).
</ResponseField>

<ResponseField name="createdAt" type="string">
  ISO timestamp when the event was generated by HIFI.
</ResponseField>

<ResponseField name="timestamp" type="string">
  ISO timestamp when the webhook was delivered.
</ResponseField>

<ResponseField name="version" type="string">
  Version of the webhook/event payload schema (e.g., v2).
</ResponseField>

## Event Types

HIFI sends webhook events for various types of operations. Each event category has a dedicated page with detailed information about the event types, payloads, and examples.

* **[User Events](/docs/webhooks/user-events)** - Events for user creation and updates
* **[KYC Events](/docs/webhooks/kyc-events)** - Events for KYC data and status changes
* **[Wallet Events](/docs/webhooks/wallet-events)** - Events for wallet transfers, bridges, and token balance updates
* **[Account Events](/docs/webhooks/account-events)** - Events for onramp, offramp, and virtual account creation and updates
* **[Onramp Events](/docs/webhooks/onramp-events)** - Events for onramp transfer creation and status updates
* **[Offramp Events](/docs/webhooks/offramp-events)** - Events for offramp transfer creation and status updates

## Verifying Webhook Signatures

**Critical:** Always verify webhook signatures to ensure requests are from HIFI and haven't been tampered with.

### Verification Process

Webhooks include a JWT token in the `Authorization` header. Verify this token using your webhook public key:

1. Extract the JWT token from the `Authorization: Bearer <token>` header
2. Verify the token using your webhook public key with RS256 algorithm
3. If verification succeeds, process the event
4. If verification fails, reject the request with 401 status

### Implementation Examples

<CodeGroup>
  ```python Python theme={null}
  from flask import Flask, request, jsonify
  import jwt

  app = Flask(**name**)
  webhook_public_key = '''-----BEGIN PUBLIC KEY-----
  your_webhook_public_key_here
  -----END 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 event
          event_type = decoded.get('eventType')
          event_data = decoded.get('data')

          # Your application logic here
          handle_event(event_type, event_data)

          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

  def handle_event(event_type, data):
  if event_type == 'WALLET.TRANSFER.UPDATE': # Update transaction status in your database
  pass
  elif event_type == 'USER.KYC.UPDATE': # Notify user of KYC status change
  pass

  if **name** == '**main**':
  app.run(port=3000)

  ```

  ```javascript Node.js theme={null}
  const express = require("express");
  const jwt = require("jsonwebtoken");

  const app = express();
  const webhookPublicKey = `-----BEGIN PUBLIC KEY-----
  your_webhook_public_key_here
  -----END PUBLIC KEY-----`;

  app.use(express.json());

  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 {
      const decoded = jwt.verify(jwtToken, webhookPublicKey, {
        algorithms: ["RS256"],
      });
      console.log("Token is valid. Decoded payload:", decoded);

      // Process the event
      const { eventType, data } = decoded;

      // Your application logic here
      handleEvent(eventType, data);

      res.status(200).send("");
    } catch (error) {
      if (error.name === "TokenExpiredError") {
        console.log("Token has expired");
        return res.status(401).send("Token has expired");
      } else {
        console.log("Failed to verify token:", error.message);
        return res.status(401).send("Token verification failed");
      }
    }
  });

  function handleEvent(eventType, data) {
    if (eventType === 'WALLET.TRANSFER.UPDATE') {
      // Update transaction status in your database
    } else if (eventType === 'USER.KYC.UPDATE') {
      // Notify user of KYC status change
    }
  }

  app.listen(3000, () => {
    console.log("Webhook server running on port 3000");
  });
  ```
</CodeGroup>

## Retry Behavior

HIFI automatically retries failed webhook deliveries to ensure reliability.

### Retry Schedule

* **Duration**: Up to 24 hours
* **Strategy**: Exponential backoff
* **Interval**: 60 seconds to 1 hour between retries
* **Success criteria**: Your endpoint returns a 2xx status code

### Retry Conditions

**HIFI retries when:**

* Your endpoint returns a 5xx error
* Connection timeout occurs
* Network error prevents delivery

**HIFI does not retry when:**

* Your endpoint returns a 4xx error (indicates a permanent failure)
* Your webhook endpoint is disabled or deleted
* 24 hours have elapsed since the original event

<Warning>
  **Endpoint availability matters:** If your endpoint is disabled when HIFI
  attempts a retry, future retries for that event are prevented. Re-enable your
  endpoint before the next retry attempt to continue receiving retries.
</Warning>

## Best Practices

<AccordionGroup>
  <Accordion title="Respond quickly">
    Return a 2xx response as soon as you receive the webhook, before processing the event. Process the event asynchronously to avoid timeouts:

    ```javascript theme={null}
    app.post('/webhook', (req, res) => {
      // Verify signature
      const event = verifyAndExtract(req);
      
      // Respond immediately
      res.status(200).send('');
      
      // Process asynchronously
      processEventAsync(event);
    });
    ```
  </Accordion>

  <Accordion title="Implement idempotency">
    Use `eventId` to ensure you process each event only once. Store processed event IDs:

    ```javascript theme={null}
    async function handleEvent(event) {
      // Check if already processed
      const processed = await db.hasProcessed(event.eventId);
      if (processed) {
        console.log('Event already processed:', event.eventId);
        return;
      }
      
      // Process event
      await processEvent(event);
      
      // Mark as processed
      await db.markProcessed(event.eventId);
    }
    ```
  </Accordion>

  <Accordion title="Handle event order">
    Events may arrive out of order. Use `createdAt` and `timestamp` to determine the correct sequence. Store the latest known state:

    ```javascript theme={null}
    function shouldProcessEvent(event, currentState) {
      const eventTime = new Date(event.createdAt);
      const lastUpdate = new Date(currentState.lastUpdatedAt);
      
      // Only process if this event is newer
      return eventTime > lastUpdate;
    }
    ```
  </Accordion>

  {" "}

  <Accordion title="Monitor webhook health">
    Track webhook delivery success rates and response times. Set up alerts for: -
    High failure rates - Slow response times - Signature verification failures
    This helps identify issues early before retries are exhausted.
  </Accordion>

  <Accordion title="Test with sandbox">
    Use the HIFI sandbox environment to test your webhook integration:

    1. Register a test webhook endpoint
    2. Trigger test events using sandbox API
    3. Verify your endpoint handles events correctly
    4. Test retry behavior by temporarily failing your endpoint
  </Accordion>
</AccordionGroup>

## Getting Help

* 📧 **Email:** [support@hifi.com](mailto:support@hifi.com)
* 💬 **Slack:** Message us in our shared Slack channel

## Related Resources

* [Wallet Transfers](/docs/transactions/transfers) - Track transfer events
* [Onramps](/docs/transactions/onramps) - Monitor onramp events
* [Offramps](/docs/transactions/offramps) - Track offramp events
* [Users](/docs/users) - Monitor user and KYC events
