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

# Webhooks Overview

> Real-time event notifications for consent and data lifecycle events

## Overview

FinPro webhooks deliver real-time HTTP POST notifications to your server when consent and data lifecycle events occur. Instead of polling the FinPro APIs for status changes, your application receives instant updates as events happen — such as when a user approves a consent, data becomes ready for fetch, or a session expires.

**Use webhooks when you need to:**

* React immediately to consent approvals (e.g., trigger an FI Request)
* Know the moment financial data is ready for retrieval
* Track consent revocations, pauses, and expirations
* Receive raw financial data pushed directly via DATA\_PUSH events

## Configuration

Set up webhooks through the FinPro Admin Portal:

<Steps>
  <Step title="Navigate to Webhooks">
    Log in to the [Admin Portal](https://console-uat.moneyone.in/scramblerpay/#/login) and navigate to **Admin → Webhooks**.
  </Step>

  <Step title="Configure your endpoint">
    Provide:

    * **Webhook URL** — Your HTTPS endpoint that will receive event notifications
    * **Secret Key** — A shared secret used for HMAC-SHA256 signature verification
    * **App Identifier** — The application/product identifier to scope events
  </Step>

  <Step title="Subscribe to event categories">
    Select the event categories you want to receive:

    * **CONSENT** — Consent lifecycle events (approved, rejected, paused, resumed, revoked, expired)
    * **DATA** — Data fetch events (ready, denied, session failed, session expired)
    * **DATA\_EXT** — Extended data events (DATA\_PUSH with inline financial data)
  </Step>
</Steps>

## Common Payload Structure

All webhook events are sent as JSON POST requests with these common fields:

| Field           | Type   | Description                                                       |
| --------------- | ------ | ----------------------------------------------------------------- |
| `timestamp`     | string | ISO 8601 timestamp of when the event occurred                     |
| `consentHandle` | string | UUID identifying the consent request                              |
| `eventType`     | string | Event category: `CONSENT`, `DATA`, or `DATA_EXT`                  |
| `eventStatus`   | string | Specific event status (e.g., `CONSENT_APPROVED`, `DATA_READY`)    |
| `consentId`     | string | UUID of the consent artifact (may be empty for rejected consents) |
| `vua`           | string | Virtual User Address of the user (e.g., `9999999999@onemoney`)    |
| `eventMessage`  | string | Human-readable description of the event                           |

### Extended Payload Fields (Type 2)

Type 2 payloads include additional fields for richer event context:

| Field           | Type   | Description                                            |
| --------------- | ------ | ------------------------------------------------------ |
| `productID`     | string | Product identifier configured in the Admin Portal      |
| `accountID`     | string | Account identifier associated with the consent request |
| `fetchType`     | string | Consent fetch type: `ONETIME` or `PERIODIC`            |
| `consentExpiry` | string | Consent expiry datetime                                |

Data events may also include:

| Field            | Type    | Description                                            |
| ---------------- | ------- | ------------------------------------------------------ |
| `dataExpiry`     | string  | ISO 8601 timestamp when fetched data expires           |
| `firstTimeFetch` | boolean | Whether this is the first data fetch for this consent  |
| `sessionId`      | string  | Session identifier (present in SESSION\_FAILED events) |

## Webhook Verification (HMAC-SHA256)

Every webhook request includes an `X-Webhook-Signature` header containing a Base64-encoded HMAC-SHA256 signature. Verify this signature to confirm the request originated from FinPro and was not tampered with.

**How it works:**

1. FinPro serializes the JSON payload body to a string
2. Computes `HMAC-SHA256(JSON.stringify(payload), your_secret_key)`
3. Base64-encodes the result
4. Sends it in the `X-Webhook-Signature` header

**To verify**, compute the same HMAC-SHA256 hash on the raw request body and compare it to the header value.

<CodeGroup>
  ```javascript Node.js theme={null}
  const crypto = require("crypto");

  function verifyWebhookSignature(rawBody, signature, secretKey) {
    const expectedSignature = crypto
      .createHmac("sha256", secretKey)
      .update(rawBody)
      .digest("base64");

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

  // Express.js example
  app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
    const signature = req.headers["x-webhook-signature"];
    const rawBody = req.body.toString();

    if (!verifyWebhookSignature(rawBody, signature, process.env.WEBHOOK_SECRET)) {
      return res.status(401).send("Invalid signature");
    }

    const event = JSON.parse(rawBody);
    // Process the event...
    res.status(200).send("OK");
  });
  ```

  ```python Python theme={null}
  import hmac
  import hashlib
  import base64

  def verify_webhook_signature(raw_body: bytes, signature: str, secret_key: str) -> bool:
      expected = base64.b64encode(
          hmac.new(
              secret_key.encode("utf-8"),
              raw_body,
              hashlib.sha256
          ).digest()
      ).decode("utf-8")

      return hmac.compare_digest(signature, expected)

  # Flask example
  @app.route("/webhook", methods=["POST"])
  def handle_webhook():
      signature = request.headers.get("X-Webhook-Signature")
      raw_body = request.get_data()

      if not verify_webhook_signature(raw_body, signature, WEBHOOK_SECRET):
          return "Invalid signature", 401

      event = request.get_json()
      # Process the event...
      return "OK", 200
  ```
</CodeGroup>

## Event Categories

| Category      | eventType  | Events                                                                                                            | Description                                |
| ------------- | ---------- | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------ |
| Consent       | `CONSENT`  | `CONSENT_APPROVED`, `CONSENT_REJECTED`, `CONSENT_PAUSED`, `CONSENT_RESUMED`, `CONSENT_REVOKED`, `CONSENT_EXPIRED` | Consent lifecycle state changes            |
| Data          | `DATA`     | `DATA_READY`, `DATA_DENIED`, `SESSION_FAILED`, `SESSION_EXPIRED`                                                  | Data fetch status updates                  |
| Data Extended | `DATA_EXT` | `DATA_PUSH`                                                                                                       | Inline data delivery with full FI payloads |

See [Consent Events](/api-reference/webhooks/consent-events) and [Data Events](/api-reference/webhooks/data-events) for detailed payload documentation.

## Endpoint Requirements

Your webhook endpoint must meet the following requirements:

| Requirement       | Details                                         |
| ----------------- | ----------------------------------------------- |
| **Protocol**      | HTTPS only                                      |
| **HTTP Method**   | Accept POST requests                            |
| **Content-Type**  | Handle `application/json` payloads              |
| **Response**      | Return HTTP `200` status to acknowledge receipt |
| **Response Time** | Respond within 10 seconds                       |

<Warning>
  If your endpoint does not return HTTP 200, FinPro will retry the webhook delivery up to 5 times.
</Warning>

## Webhook Payload Encryption

FinPro supports optional **end-to-end encryption** on webhook payloads. When enabled, the webhook body is delivered as an encrypted string instead of plaintext JSON — protecting sensitive financial event data in transit. The same hybrid encryption scheme used for API payloads applies here: a symmetric key encrypts the payload, and an asymmetric key secures the key exchange.

<Note>
  To enable webhook encryption, you must register your RSA public key with FinPro. FinPro uses your registered public key to encrypt the AES key and IV for each webhook delivery. Contact support to register your key and enable this for your endpoint.
</Note>

When encryption is enabled, the POST body changes from the standard JSON structure to:

```json theme={null}
{
  "RequestEncryptedValue": "<base64(ciphertext) + base64(auth_tag)>"
}
```

Each encrypted webhook includes a `kek` header containing the encrypted `key:iv` string — encrypted with **your registered RSA public key**. A fresh AES key and IV are generated per webhook delivery.

**To decrypt an incoming webhook:**

<Steps>
  <Step title="Verify the signature first">
    Validate `X-Webhook-Signature` against the **raw encrypted** request body before decrypting. Do not parse or modify the body first.
  </Step>

  <Step title="Decrypt the KEK header">
    Read the `kek` header and RSA-decrypt it using **your RSA private key** to recover the `key:iv` string.
  </Step>

  <Step title="Extract key and IV">
    Split `key:iv` on `:` — the first 32 chars are the AES key, the remaining 12 chars are the IV.
  </Step>

  <Step title="Split the payload">
    Take `RequestEncryptedValue` — last 24 chars = `base64(auth_tag)`, everything before = `base64(ciphertext)`.
  </Step>

  <Step title="Decrypt and parse">
    AES-256-GCM decrypt using the recovered key and IV, then parse the decrypted bytes as JSON to get the standard webhook payload.
  </Step>
</Steps>

<Note>
  See the [Client Encryption guide](/guides/features/client-encryption) for full algorithm details, key specifications, and code examples.
</Note>

## Best Practices

<AccordionGroup>
  <Accordion title="Security">
    * **Always verify signatures** — Validate the `X-Webhook-Signature` header on every request using HMAC-SHA256
    * **Use HTTPS only** — Never expose webhook endpoints over plain HTTP
    * **Keep secrets secure** — Store your webhook secret key in environment variables, not in source code
    * **Validate payloads** — Check that `eventType` and `eventStatus` values are expected before processing
  </Accordion>

  <Accordion title="Reliability">
    * **Respond quickly** — Return HTTP 200 immediately, then process the event asynchronously
    * **Handle retries** — FinPro retries failed deliveries up to 5 times, so your endpoint may receive the same event more than once
    * **Idempotency** — Use the `consentHandle` + `eventStatus` + `timestamp` combination to detect and deduplicate repeated deliveries
    * **Queue processing** — For high-volume integrations, enqueue events for background processing rather than handling inline
  </Accordion>

  <Accordion title="Monitoring">
    * **Log all events** — Record incoming webhook payloads for debugging and audit trails
    * **Alert on failures** — Monitor your endpoint's error rate and response times
    * **Track event types** — Monitor which events you receive to verify your subscription is configured correctly
  </Accordion>
</AccordionGroup>
