# Overview

> Configure webhooks to receive real-time HTTP notifications for the hosted onboarding, transaction and deposit events raised through your API keys.

Webhooks allow you to receive real-time HTTP notifications of events relating to hosted customer onboarding, transactions, or deposits initiated through your API Keys.

It is essential to subscribe to the relevant webhooks, as it is Noah's method to notify you of updates, for which you may need to action processes within your application to, for example, credit or debit the accounts involved in a given transaction.

- **Customer Events** are triggered when the customer has completed the hosted onboarding flow and
  submitted the form at the end of the process, as well as when the customer's hosted
  onboarding status changes. For details, see [Customer](../webhooks/customer.md).
- **Fiat Deposit Events** are triggered when a customer performs a Fiat Deposit to an assigned bank account number, and again when `Status`, `Refunds`, `SubStatus`, or `RFI` change. For details, see [FiatDeposit](../webhooks/fiat-deposits.md).
- **Transaction Events** are triggered when a transaction is created or when `Status`, `Refunds`, `SubStatus`, or `RFI` change. For details, see [Transaction](../webhooks/transactions.md).

## Create a Webhook Subscription

### 1. Navigate to Configuration

Log in to the dashboard with your Noah Business Account and navigate to the API Configuration page, then click on the Webhooks tab. Here you can create a new webhook subscription via the User Interface by providing your subscription URL.

### 2. Configure Webhook Subscriptions

You will be prompted to enter the following details for each webhook subscription:

- **URL**: This is the endpoint where you want to receive webhook notifications.
- **EventTypes**: A list of event types you can subscribe to. Noah supports the '[Customer](../webhooks/customer.md)', '[FiatDeposit](../webhooks/fiat-deposits.md)', and '[Transaction](../webhooks/transactions.md)' event types.

### 3. Confirm Webhook Subscription Receipt

Your system will receive webhook notifications at the specified subscription URL for every hosted customer onboarding, transaction, or fiat deposit update. These notifications contain detailed information about the event, allowing your system to respond appropriately and in real-time.

Ensure that it's functioning correctly by triggering an event in the system. The webhook includes the version and occurrence time of the event to allow you to process the latest version of the event.

### 4. Secure Your Endpoint

Noah cryptographically signs all webhook requests sent to your endpoint. You should verify these signatures to confirm that the webhook genuinely originated from Noah and has not been tampered with.

**How it works:**

1. Noah signs the request body using our **private key** (ECDSA with SHA-384)
2. The signature is included in the `Webhook-Signature` header (base64-encoded)
3. You verify the signature using **Noah's public key** (provided below)

:::note
The public keys below are **Noah's public keys** for you to use when verifying webhooks. You do not need to generate or provide any keys to Noah — we handle the signing, you handle the verification.
:::

**Noah's Webhook Public Keys**

Sandbox:

```
-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEm8yBiD+kmVJ1Xc9sfRkDx0yo9+u8yiAD
PngI20KoEswz0gflp8o/z66Abqz/m9A1CBecixWdeT72pA8NZBJI6L6Osd8RV+yx
QArxeGKEVX/2QNrfPqeAKODHT5LdStGT
-----END PUBLIC KEY-----
```

Production:

```
-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAELKJhxcUGJr3XgRrf+laSAVHvp31wFhE2
XdicXvF0DAdKzSPN8bkSdjrsUA6nnVUq3M47Y7RUYugMfkagaYjUExQZVjpMFg0P
DnXWl9y0dXYDq+pzYhAgL+MNpnY0eJ78
-----END PUBLIC KEY-----
```

<details>

<summary>Node.js Verification Example</summary>

```javascript
const crypto = require('crypto');

const NOAH_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEm8yBiD+kmVJ1Xc9sfRkDx0yo9+u8yiAD
PngI20KoEswz0gflp8o/z66Abqz/m9A1CBecixWdeT72pA8NZBJI6L6Osd8RV+yx
QArxeGKEVX/2QNrfPqeAKODHT5LdStGT
-----END PUBLIC KEY-----`;

function verifyWebhookSignature(requestBody, signatureHeader) {
  const signature = Buffer.from(signatureHeader, 'base64');
  const verifier = crypto.createVerify('SHA384');
  verifier.update(requestBody);
  return verifier.verify(NOAH_PUBLIC_KEY, signature);
}
```

---

</details>

<details>

<summary>Python Verification Example</summary>

```python
import base64
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.serialization import load_pem_public_key
from cryptography.exceptions import InvalidSignature

NOAH_PUBLIC_KEY = b"""-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEm8yBiD+kmVJ1Xc9sfRkDx0yo9+u8yiAD
PngI20KoEswz0gflp8o/z66Abqz/m9A1CBecixWdeT72pA8NZBJI6L6Osd8RV+yx
QArxeGKEVX/2QNrfPqeAKODHT5LdStGT
-----END PUBLIC KEY-----"""

def verify_webhook_signature(request_body: bytes, signature_header: str) -> bool:
    try:
        signature = base64.b64decode(signature_header)
        public_key = load_pem_public_key(NOAH_PUBLIC_KEY)
        public_key.verify(signature, request_body, ec.ECDSA(hashes.SHA384()))
        return True
    except InvalidSignature:
        return False
```

---

</details>

<details>

<summary>Go Verification Example</summary>

```go
import (
	"crypto/ecdsa"
	"crypto/sha512"
	"crypto/x509"
	"encoding/base64"
	"encoding/pem"
)

const noahPublicKeyPEM = `-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEm8yBiD+kmVJ1Xc9sfRkDx0yo9+u8yiAD
PngI20KoEswz0gflp8o/z66Abqz/m9A1CBecixWdeT72pA8NZBJI6L6Osd8RV+yx
QArxeGKEVX/2QNrfPqeAKODHT5LdStGT
-----END PUBLIC KEY-----`

func verifyWebhookSignature(body []byte, signatureHeader string) bool {
	signature, err := base64.StdEncoding.DecodeString(signatureHeader)
	if err != nil {
		return false
	}
	block, _ := pem.Decode([]byte(noahPublicKeyPEM))
	pub, err := x509.ParsePKIXPublicKey(block.Bytes)
	if err != nil {
		return false
	}
	ecdsaPub, ok := pub.(*ecdsa.PublicKey)
	if !ok {
		return false
	}
	hash := sha512.Sum384(body)
	return ecdsa.VerifyASN1(ecdsaPub, hash[:], signature)
}
```

---

</details>

<details>

<summary>Java Verification Example</summary>

```java
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

public class WebhookVerifier {
    private static final String NOAH_PUBLIC_KEY =
        "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEm8yBiD+kmVJ1Xc9sfRkDx0yo9+u8yiAD" +
        "PngI20KoEswz0gflp8o/z66Abqz/m9A1CBecixWdeT72pA8NZBJI6L6Osd8RV+yx" +
        "QArxeGKEVX/2QNrfPqeAKODHT5LdStGT";

    public static boolean verifyWebhookSignature(byte[] requestBody, String signatureHeader) {
        try {
            byte[] signatureBytes = Base64.getDecoder().decode(signatureHeader);
            byte[] publicKeyBytes = Base64.getDecoder().decode(NOAH_PUBLIC_KEY);

            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance("EC");
            PublicKey publicKey = keyFactory.generatePublic(keySpec);

            Signature signature = Signature.getInstance("SHA384withECDSA");
            signature.initVerify(publicKey);
            signature.update(requestBody);

            return signature.verify(signatureBytes);
        } catch (Exception e) {
            return false;
        }
    }
}
```

---

</details>

### 5. Webhook Acknowledgment and Retries

- **Acknowledgment Required**: Ensure your endpoint responds with a `2xx` status (e.g. `200`) to acknowledge the receipt of webhook notifications. Any non-`2xx` response is treated as a delivery failure and will be retried.
- **Retry Mechanism**: If a notification fails to deliver, Noah will retry up to 7 times with increasing intervals between each attempt. The parameters for this are defined as:
  - Interval: `12 seconds`
  - Max Retries: `7`
  - Exponential backoff: `5`

## Understanding Webhook Behavior

### Why Multiple Webhooks?

A single user action (such as a fiat deposit) triggers multiple webhooks as it progresses through the system. Each webhook represents a meaningful update, not a duplicate notification. For example, a successful Bank OnRamp flow (fiat deposit → crypto payout) can generate multiple webhook events for `Status`, and additional events while still `Pending` when `SubStatus` or `RFI` change.

### Status Values

`Transaction` and `FiatDeposit` webhooks use these status values:

| Status    | Meaning                                        |
| --------- | ---------------------------------------------- |
| `Pending` | Processing in progress                         |
| `Settled` | Successfully completed                         |
| `Failed`  | Did not complete; check for refund information |

Transitions: `Pending` → `Settled` OR `Pending` → `Failed`

While `Pending`, payloads may also include:

- `SubStatus` — `AmlScreening`, `UnderReview`, `Submitted`, `Confirming`
- `RFI` — `Status` (`AwaitingCustomer`, `UnderReview`, `Closed`, `Completed`) and `Type` (`Manual`, `ProofOfAddress`)

Changes to `SubStatus` or any field of `RFI` also trigger webhooks. See [Transaction Event](../webhooks/transactions.md#substatus-and-rfi) and [FiatDeposit Event](../webhooks/fiat-deposits.md).

Customer verification statuses are different: `Pending`, `Approved`, `Declined` (see [Customer](../webhooks/customer.md)).

### Handling Webhook Order

Webhook delivery order is not guaranteed. Your system must handle out-of-order events.

Use these fields to determine the latest state:

- `EventVersion` — Timestamp-based version number (higher = newer)
- `Occurred` — When the event happened

**Recommended approach:** Store `EventVersion` per resource ID. Only process webhooks where incoming `EventVersion` is more recent than stored `EventVersion`.

## Important Notes

- Ensure your endpoint is secured and can handle incoming HTTP requests.
- Always test your webhook to confirm it's receiving and handling data as expected.
- Monitor your endpoint for uptime and reliability.
- The delivery order of webhook notifications is not guaranteed. Ensure your system can handle out-of-order events appropriately to maintain data consistency.

## Whitelisting

Your service may require IP Address whitelisting to receive webhook notifications, if so, whitelist the following Noah IP addresses - which apply to both the Sandbox and Production environments.

- `35.178.216.8`
- `3.10.163.222`
