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

# Webhooks

> Receive real-time notifications when resource statuses change

Webhooks notify your application whenever a resource changes status. Instead of polling the API, you receive an HTTP POST request to your endpoint with the updated data.

## Setting up webhooks

To configure webhooks, go to **Developers > Webhooks** in the [Dashboard](https://dashboard.lumx.io) and add your endpoint URL. You can subscribe to specific event types or receive all events.

<Note>
  You must be a project admin or owner to create webhooks.
</Note>

## Event structure

Every webhook event follows a consistent structure with three top-level fields:

```json theme={null}
{
  "eventId": "550e8400-e29b-41d4-a716-446655440000",
  "eventType": "onramp.success",
  "data": {
    // Event-specific payload
  }
}
```

| Field       | Type     | Description                                                  |
| :---------- | :------- | :----------------------------------------------------------- |
| `eventId`   | `string` | Unique identifier for the event (UUID)                       |
| `eventType` | `string` | The event type (e.g., `onramp.success`, `customer.approved`) |
| `data`      | `object` | The event payload, which varies by event type                |

## Available events

<AccordionGroup>
  <Accordion title="On-ramp events">
    | Event type                       | Triggered when                                     |
    | :------------------------------- | :------------------------------------------------- |
    | `onramp.awaiting_funds`          | On-ramp is waiting for the customer to send funds  |
    | `onramp.transferring_fiat`       | Fiat transfer is in progress                       |
    | `onramp.trading`                 | Trade is being executed                            |
    | `onramp.transferring_stablecoin` | Stablecoin transfer to the customer is in progress |
    | `onramp.success`                 | On-ramp completed successfully                     |
    | `onramp.failed`                  | On-ramp failed                                     |
    | `onramp.expired`                 | On-ramp expired before payment was received        |
  </Accordion>

  <Accordion title="Off-ramp events">
    | Event type                        | Triggered when                                       |
    | :-------------------------------- | :--------------------------------------------------- |
    | `offramp.transferring_stablecoin` | Stablecoin transfer from the customer is in progress |
    | `offramp.trading`                 | Trade is being executed                              |
    | `offramp.transferring_fiat`       | Fiat transfer to the destination is in progress      |
    | `offramp.success`                 | Off-ramp completed successfully                      |
    | `offramp.failed`                  | Off-ramp failed                                      |
  </Accordion>

  <Accordion title="Transfer events">
    | Event type                         | Triggered when                     |
    | :--------------------------------- | :--------------------------------- |
    | `transfer.transferring_stablecoin` | Stablecoin transfer is in progress |
    | `transfer.success`                 | Transfer completed successfully    |
    | `transfer.failed`                  | Transfer failed                    |
  </Accordion>

  <Accordion title="Customer events">
    | Event type                    | Triggered when                                          |
    | :---------------------------- | :------------------------------------------------------ |
    | `customer.created`            | Customer was created                                    |
    | `customer.under_verification` | Customer verification is in progress                    |
    | `customer.approved`           | Customer was approved                                   |
    | `customer.rfi`                | Customer requires additional information (can resubmit) |
    | `customer.final_rejection`    | Customer was permanently rejected                       |
  </Accordion>

  <Accordion title="Account events">
    | Event type             | Triggered when                                              |
    | :--------------------- | :---------------------------------------------------------- |
    | `account.provisioning` | Account is being provisioned and verified                   |
    | `account.rfi`          | Additional information is required to continue verification |
    | `account.active`       | Account is verified and can receive funds                   |
    | `account.closed`       | Account was permanently closed                              |
  </Accordion>

  <Accordion title="Destination events">
    | Event type                        | Triggered when                          |
    | :-------------------------------- | :-------------------------------------- |
    | `destinations.under_verification` | Destination verification is in progress |
    | `destinations.approved`           | Destination was approved                |
    | `destinations.final_rejection`    | Destination was permanently rejected    |
  </Accordion>
</AccordionGroup>

## Payload examples

<Tabs>
  <Tab title="On-ramp">
    <Tabs>
      <Tab title="awaiting_funds">
        ```json theme={null}
        {
          "eventId": "550e8400-e29b-41d4-a716-446655440000",
          "eventType": "onramp.awaiting_funds",
          "data": {
            "id": "123e4567-e89b-12d3-a456-426614174000",
            "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
            "type": "ON_RAMP",
            "request": {
              "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
              "rail": "PIX",
              "sourceCurrency": "BRL",
              "sourceAmount": "10000.00",
              "targetCurrency": "USDC",
              "purpose": "PERSONAL_ACCOUNT"
            },
            "state": {
              "status": "AWAITING_FUNDS",
              "payment": {
                "rail": "PIX",
                "brCode": "00020126580014br.gov.bcb.pix0136123e4567-e89b-12d3-a456-4266141740005204000053039865802BR5910Lumx Test6009Sao Paulo62070503***6304ABCD"
              }
            },
            "metadata": {},
            "createdAt": "2024-03-20T15:30:00Z",
            "updatedAt": "2024-03-20T15:30:05Z"
          }
        }
        ```
      </Tab>

      <Tab title="transferring_fiat">
        ```json theme={null}
        {
          "eventId": "550e8400-e29b-41d4-a716-446655440000",
          "eventType": "onramp.transferring_fiat",
          "data": {
            "id": "123e4567-e89b-12d3-a456-426614174000",
            "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
            "type": "ON_RAMP",
            "request": {
              "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
              "rail": "PIX",
              "sourceCurrency": "BRL",
              "sourceAmount": "10000.00",
              "targetCurrency": "USDC",
              "purpose": "PERSONAL_ACCOUNT"
            },
            "state": {
              "status": "TRANSFERRING_FIAT",
              "payment": {
                "rail": "PIX",
                "brCode": "00020126580014br.gov.bcb.pix0136123e4567-e89b-12d3-a456-4266141740005204000053039865802BR5910Lumx Test6009Sao Paulo62070503***6304ABCD"
              }
            },
            "metadata": {},
            "createdAt": "2024-03-20T15:30:00Z",
            "updatedAt": "2024-03-20T15:30:05Z"
          }
        }
        ```
      </Tab>

      <Tab title="trading">
        ```json theme={null}
        {
          "eventId": "550e8400-e29b-41d4-a716-446655440000",
          "eventType": "onramp.trading",
          "data": {
            "id": "123e4567-e89b-12d3-a456-426614174000",
            "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
            "type": "ON_RAMP",
            "request": {
              "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
              "rail": "PIX",
              "sourceCurrency": "BRL",
              "sourceAmount": "10000.00",
              "targetCurrency": "USDC",
              "purpose": "PERSONAL_ACCOUNT"
            },
            "state": {
              "status": "TRADING",
              "payment": {
                "rail": "PIX",
                "brCode": "00020126580014br.gov.bcb.pix0136123e4567-e89b-12d3-a456-4266141740005204000053039865802BR5910Lumx Test6009Sao Paulo62070503***6304ABCD"
              }
            },
            "metadata": {},
            "createdAt": "2024-03-20T15:30:00Z",
            "updatedAt": "2024-03-20T15:30:05Z"
          }
        }
        ```
      </Tab>

      <Tab title="transferring_stablecoin">
        ```json theme={null}
        {
          "eventId": "550e8400-e29b-41d4-a716-446655440000",
          "eventType": "onramp.transferring_stablecoin",
          "data": {
            "id": "123e4567-e89b-12d3-a456-426614174000",
            "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
            "type": "ON_RAMP",
            "request": {
              "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
              "rail": "PIX",
              "sourceCurrency": "BRL",
              "sourceAmount": "10000.00",
              "targetCurrency": "USDC",
              "purpose": "PERSONAL_ACCOUNT"
            },
            "state": {
              "status": "TRANSFERRING_STABLECOIN",
              "payment": {
                "rail": "PIX",
                "brCode": "00020126580014br.gov.bcb.pix0136123e4567-e89b-12d3-a456-4266141740005204000053039865802BR5910Lumx Test6009Sao Paulo62070503***6304ABCD"
              }
            },
            "metadata": {},
            "createdAt": "2024-03-20T15:30:00Z",
            "updatedAt": "2024-03-20T15:30:05Z"
          }
        }
        ```
      </Tab>

      <Tab title="success">
        ```json theme={null}
        {
          "eventId": "550e8400-e29b-41d4-a716-446655440000",
          "eventType": "onramp.success",
          "data": {
            "id": "123e4567-e89b-12d3-a456-426614174000",
            "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
            "type": "ON_RAMP",
            "request": {
              "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
              "rail": "PIX",
              "sourceCurrency": "BRL",
              "sourceAmount": "10000.00",
              "targetCurrency": "USDC",
              "purpose": "PERSONAL_ACCOUNT"
            },
            "state": {
              "status": "SUCCESS",
              "payment": {
                "rail": "PIX",
                "brCode": "00020126580014br.gov.bcb.pix0136123e4567-e89b-12d3-a456-4266141740005204000053039865802BR5910Lumx Test6009Sao Paulo62070503***6304ABCD"
              },
              "receipt": {
                "sourceCurrency": "BRL",
                "sourceAmount": "10000.00",
                "targetCurrency": "USDC",
                "baseExchangeRate": "5.25",
                "baseTargetAmount": "1904.76",
                "fees": {
                  "lumx": {
                    "rate": "0.005",
                    "flatAmount": "0.00",
                    "totalAmount": "9.52",
                    "currency": "USDC"
                  },
                  "partner": {
                    "rate": "0.002",
                    "flatAmount": "0.00",
                    "totalAmount": "3.81",
                    "currency": "USDC"
                  }
                },
                "finalTargetAmount": "1891.43",
                "finalExchangeRate": "5.29",
                "transactionHash": "0xabc123def456abc123def456abc123def456abc123def456abc123def456abc1",
                "blockExplorerUrl": "https://polygonscan.com/tx/0xabc123def456abc123def456abc123def456abc123def456abc123def456abc1"
              }
            },
            "metadata": {},
            "createdAt": "2024-03-20T15:30:00Z",
            "updatedAt": "2024-03-20T15:30:05Z"
          }
        }
        ```
      </Tab>

      <Tab title="failed">
        ```json theme={null}
        {
          "eventId": "550e8400-e29b-41d4-a716-446655440000",
          "eventType": "onramp.failed",
          "data": {
            "id": "123e4567-e89b-12d3-a456-426614174000",
            "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
            "type": "ON_RAMP",
            "request": {
              "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
              "rail": "PIX",
              "sourceCurrency": "BRL",
              "sourceAmount": "10000.00",
              "targetCurrency": "USDC",
              "purpose": "PERSONAL_ACCOUNT"
            },
            "state": {
              "status": "FAILED",
              "payment": {
                "rail": "PIX",
                "brCode": "00020126580014br.gov.bcb.pix0136123e4567-e89b-12d3-a456-4266141740005204000053039865802BR5910Lumx Test6009Sao Paulo62070503***6304ABCD"
              },
              "receipt": {
                "error": {
                  "message": "Your transaction has failed. Please contact support."
                }
              }
            },
            "metadata": {},
            "createdAt": "2024-03-20T15:30:00Z",
            "updatedAt": "2024-03-20T15:30:05Z"
          }
        }
        ```
      </Tab>

      <Tab title="expired">
        ```json theme={null}
        {
          "eventId": "550e8400-e29b-41d4-a716-446655440000",
          "eventType": "onramp.expired",
          "data": {
            "id": "123e4567-e89b-12d3-a456-426614174000",
            "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
            "type": "ON_RAMP",
            "request": {
              "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
              "rail": "PIX",
              "sourceCurrency": "BRL",
              "sourceAmount": "10000.00",
              "targetCurrency": "USDC",
              "purpose": "PERSONAL_ACCOUNT"
            },
            "state": {
              "status": "EXPIRED",
              "payment": {
                "rail": "PIX",
                "brCode": "00020126580014br.gov.bcb.pix0136123e4567-e89b-12d3-a456-4266141740005204000053039865802BR5910Lumx Test6009Sao Paulo62070503***6304ABCD"
              }
            },
            "metadata": {},
            "createdAt": "2024-03-20T15:30:00Z",
            "updatedAt": "2024-03-20T15:30:05Z"
          }
        }
        ```
      </Tab>
    </Tabs>
  </Tab>

  <Tab title="Off-ramp">
    <Tabs>
      <Tab title="transferring_stablecoin">
        ```json theme={null}
        {
          "eventId": "550e8400-e29b-41d4-a716-446655440000",
          "eventType": "offramp.transferring_stablecoin",
          "data": {
            "id": "123e4567-e89b-12d3-a456-426614174000",
            "destinationId": "ba123456-e89b-12d3-a456-426614174000",
            "type": "OFF_RAMP",
            "request": {
              "destinationId": "e80d3137-eddd-4791-b9b8-6e36b289f284",
              "sourceCurrency": "USDC",
              "sourceAmount": "1000.00",
              "purpose": "PERSONAL_ACCOUNT"
            },
            "state": {
              "status": "TRANSFERRING_STABLECOIN"
            },
            "metadata": {},
            "createdAt": "2024-03-20T15:30:00Z",
            "updatedAt": "2024-03-20T15:30:05Z"
          }
        }
        ```
      </Tab>

      <Tab title="trading">
        ```json theme={null}
        {
          "eventId": "550e8400-e29b-41d4-a716-446655440000",
          "eventType": "offramp.trading",
          "data": {
            "id": "123e4567-e89b-12d3-a456-426614174000",
            "destinationId": "ba123456-e89b-12d3-a456-426614174000",
            "type": "OFF_RAMP",
            "request": {
              "destinationId": "e80d3137-eddd-4791-b9b8-6e36b289f284",
              "sourceCurrency": "USDC",
              "sourceAmount": "1000.00",
              "purpose": "PERSONAL_ACCOUNT"
            },
            "state": {
              "status": "TRADING"
            },
            "metadata": {},
            "createdAt": "2024-03-20T15:30:00Z",
            "updatedAt": "2024-03-20T15:30:05Z"
          }
        }
        ```
      </Tab>

      <Tab title="transferring_fiat">
        ```json theme={null}
        {
          "eventId": "550e8400-e29b-41d4-a716-446655440000",
          "eventType": "offramp.transferring_fiat",
          "data": {
            "id": "123e4567-e89b-12d3-a456-426614174000",
            "destinationId": "ba123456-e89b-12d3-a456-426614174000",
            "type": "OFF_RAMP",
            "request": {
              "destinationId": "e80d3137-eddd-4791-b9b8-6e36b289f284",
              "sourceCurrency": "USDC",
              "sourceAmount": "1000.00",
              "purpose": "PERSONAL_ACCOUNT"
            },
            "state": {
              "status": "TRANSFERRING_FIAT"
            },
            "metadata": {},
            "createdAt": "2024-03-20T15:30:00Z",
            "updatedAt": "2024-03-20T15:30:05Z"
          }
        }
        ```
      </Tab>

      <Tab title="success">
        ```json theme={null}
        {
          "eventId": "550e8400-e29b-41d4-a716-446655440000",
          "eventType": "offramp.success",
          "data": {
            "id": "123e4567-e89b-12d3-a456-426614174000",
            "destinationId": "ba123456-e89b-12d3-a456-426614174000",
            "type": "OFF_RAMP",
            "request": {
              "destinationId": "e80d3137-eddd-4791-b9b8-6e36b289f284",
              "sourceCurrency": "USDC",
              "sourceAmount": "1000.00",
              "purpose": "PERSONAL_ACCOUNT"
            },
            "state": {
              "status": "SUCCESS",
              "receipt": {
                "sourceCurrency": "USDC",
                "sourceAmount": "1904.76",
                "targetCurrency": "BRL",
                "baseExchangeRate": "5.25",
                "baseTargetAmount": "10000.00",
                "fees": {
                  "lumx": {
                    "rate": "0.005",
                    "flatAmount": "0.00",
                    "totalAmount": "9.52",
                    "currency": "USDC"
                  },
                  "partner": {
                    "rate": "0.002",
                    "flatAmount": "0.00",
                    "totalAmount": "3.81",
                    "currency": "USDC"
                  }
                },
                "finalTargetAmount": "9900.00",
                "finalExchangeRate": "5.20",
                "transactionHash": "0xabc123def456abc123def456abc123def456abc123def456abc123def456abc1",
                "blockExplorerUrl": "https://polygonscan.com/tx/0xabc123def456abc123def456abc123def456abc123def456abc123def456abc1"
              }
            },
            "metadata": {},
            "createdAt": "2024-03-20T15:30:00Z",
            "updatedAt": "2024-03-20T15:30:05Z"
          }
        }
        ```
      </Tab>

      <Tab title="failed">
        ```json theme={null}
        {
          "eventId": "550e8400-e29b-41d4-a716-446655440000",
          "eventType": "offramp.failed",
          "data": {
            "id": "123e4567-e89b-12d3-a456-426614174000",
            "destinationId": "ba123456-e89b-12d3-a456-426614174000",
            "type": "OFF_RAMP",
            "request": {
              "destinationId": "e80d3137-eddd-4791-b9b8-6e36b289f284",
              "sourceCurrency": "USDC",
              "sourceAmount": "1000.00",
              "purpose": "PERSONAL_ACCOUNT"
            },
            "state": {
              "status": "FAILED",
              "receipt": {
                "error": {
                  "message": "Your transaction has failed. Please contact support."
                }
              }
            },
            "metadata": {},
            "createdAt": "2024-03-20T15:30:00Z",
            "updatedAt": "2024-03-20T15:30:05Z"
          }
        }
        ```
      </Tab>
    </Tabs>
  </Tab>

  <Tab title="Transfer">
    <Tabs>
      <Tab title="transferring_stablecoin">
        ```json theme={null}
        {
          "eventId": "550e8400-e29b-41d4-a716-446655440000",
          "eventType": "transfer.transferring_stablecoin",
          "data": {
            "id": "123e4567-e89b-12d3-a456-426614174000",
            "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
            "type": "TRANSFER",
            "request": {
              "currency": "USDC",
              "from": "123e4567-e89b-12d3-a456-426614174001",
              "to": "123e4567-e89b-12d3-a456-426614174002",
              "amount": "1000.000000"
            },
            "state": {
              "status": "TRANSFERRING_STABLECOIN"
            },
            "metadata": {},
            "createdAt": "2024-03-20T15:30:00Z",
            "updatedAt": "2024-03-20T15:30:05Z"
          }
        }
        ```
      </Tab>

      <Tab title="success">
        ```json theme={null}
        {
          "eventId": "550e8400-e29b-41d4-a716-446655440000",
          "eventType": "transfer.success",
          "data": {
            "id": "123e4567-e89b-12d3-a456-426614174000",
            "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
            "type": "TRANSFER",
            "request": {
              "currency": "USDC",
              "from": "123e4567-e89b-12d3-a456-426614174001",
              "to": "123e4567-e89b-12d3-a456-426614174002",
              "amount": "1000.000000"
            },
            "state": {
              "status": "SUCCESS",
              "receipt": {
                "transactionHash": "0xabc123def456abc123def456abc123def456abc123def456abc123def456abc1",
                "blockExplorerUrl": "https://polygonscan.com/tx/0xabc123def456abc123def456abc123def456abc123def456abc123def456abc1"
              }
            },
            "metadata": {},
            "createdAt": "2024-03-20T15:30:00Z",
            "updatedAt": "2024-03-20T15:30:05Z"
          }
        }
        ```
      </Tab>

      <Tab title="failed">
        ```json theme={null}
        {
          "eventId": "550e8400-e29b-41d4-a716-446655440000",
          "eventType": "transfer.failed",
          "data": {
            "id": "123e4567-e89b-12d3-a456-426614174000",
            "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
            "type": "TRANSFER",
            "request": {
              "currency": "USDC",
              "from": "123e4567-e89b-12d3-a456-426614174001",
              "to": "123e4567-e89b-12d3-a456-426614174002",
              "amount": "1000.000000"
            },
            "state": {
              "status": "FAILED",
              "receipt": {
                "error": {
                  "message": "Your transaction has failed. Please contact support."
                }
              }
            },
            "metadata": {},
            "createdAt": "2024-03-20T15:30:00Z",
            "updatedAt": "2024-03-20T15:30:05Z"
          }
        }
        ```
      </Tab>
    </Tabs>
  </Tab>

  <Tab title="Customer">
    <Tabs>
      <Tab title="created">
        ```json theme={null}
        {
          "eventId": "550e8400-e29b-41d4-a716-446655440000",
          "eventType": "customer.created",
          "data": {
            "id": "7f3e1a2b-4c5d-6e7f-8a9b-0c1d2e3f4a5b",
            "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
            "status": "created",
            "level": "BASIC",
            "associatedParties": [
              {
                "id": "f0e9d8c7-b6a5-4f3e-2d1c-0b9a8f7e6d5c",
                "associatedPartyId": "c1d2e3f4-a5b6-7c8d-9e0f-a1b2c3d4e5f6",
                "roles": ["SHAREHOLDER"],
                "status": "approved",
                "level": "BASIC"
              }
            ],
            "metadata": {},
            "createdAt": "2024-03-20T15:30:00Z",
            "updatedAt": "2024-03-20T15:30:05Z"
          }
        }
        ```
      </Tab>

      <Tab title="under_verification">
        ```json theme={null}
        {
          "eventId": "550e8400-e29b-41d4-a716-446655440000",
          "eventType": "customer.under_verification",
          "data": {
            "id": "7f3e1a2b-4c5d-6e7f-8a9b-0c1d2e3f4a5b",
            "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
            "status": "under_verification",
            "level": "BASIC",
            "associatedParties": [
              {
                "id": "f0e9d8c7-b6a5-4f3e-2d1c-0b9a8f7e6d5c",
                "associatedPartyId": "c1d2e3f4-a5b6-7c8d-9e0f-a1b2c3d4e5f6",
                "roles": ["SHAREHOLDER"],
                "status": "approved",
                "level": "BASIC"
              }
            ],
            "metadata": {},
            "createdAt": "2024-03-20T15:30:00Z",
            "updatedAt": "2024-03-20T15:30:05Z"
          }
        }
        ```
      </Tab>

      <Tab title="approved">
        ```json theme={null}
        {
          "eventId": "550e8400-e29b-41d4-a716-446655440000",
          "eventType": "customer.approved",
          "data": {
            "id": "7f3e1a2b-4c5d-6e7f-8a9b-0c1d2e3f4a5b",
            "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
            "status": "approved",
            "level": "BASIC",
            "associatedParties": [
              {
                "id": "f0e9d8c7-b6a5-4f3e-2d1c-0b9a8f7e6d5c",
                "associatedPartyId": "c1d2e3f4-a5b6-7c8d-9e0f-a1b2c3d4e5f6",
                "roles": ["SHAREHOLDER"],
                "status": "approved",
                "level": "BASIC"
              }
            ],
            "metadata": {},
            "createdAt": "2024-03-20T15:30:00Z",
            "updatedAt": "2024-03-20T15:30:05Z"
          }
        }
        ```
      </Tab>

      <Tab title="rfi">
        ```json theme={null}
        {
          "eventId": "550e8400-e29b-41d4-a716-446655440000",
          "eventType": "customer.rfi",
          "data": {
            "id": "7f3e1a2b-4c5d-6e7f-8a9b-0c1d2e3f4a5b",
            "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
            "status": "rfi",
            "level": "BASIC",
            "statusReason": {
              "rejectLabels": ["DOCUMENT_QUALITY"],
              "rejectSubLabels": ["DOCUMENT_BLURRY"],
              "documents": [
                {
                  "type": "PASSPORT",
                  "status": "DECLINED",
                  "comment": "Document is blurry and illegible",
                  "rejectLabels": ["DOCUMENT_QUALITY"]
                }
              ]
            },
            "associatedParties": [
              {
                "id": "f0e9d8c7-b6a5-4f3e-2d1c-0b9a8f7e6d5c",
                "associatedPartyId": "c1d2e3f4-a5b6-7c8d-9e0f-a1b2c3d4e5f6",
                "roles": ["SHAREHOLDER"],
                "status": "rfi",
                "level": "BASIC",
                "statusReason": {
                  "rejectLabels": ["DOCUMENT_QUALITY"],
                  "rejectSubLabels": ["DOCUMENT_BLURRY"]
                }
              }
            ],
            "metadata": {},
            "createdAt": "2024-03-20T15:30:00Z",
            "updatedAt": "2024-03-20T15:30:05Z"
          }
        }
        ```
      </Tab>

      <Tab title="final_rejection">
        ```json theme={null}
        {
          "eventId": "550e8400-e29b-41d4-a716-446655440000",
          "eventType": "customer.final_rejection",
          "data": {
            "id": "7f3e1a2b-4c5d-6e7f-8a9b-0c1d2e3f4a5b",
            "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
            "status": "final_rejection",
            "level": "BASIC",
            "statusReason": {
              "rejectLabels": ["FRAUDULENT_PATTERNS"],
              "rejectSubLabels": ["MULTIPLE_SUBMISSIONS"]
            },
            "associatedParties": [
              {
                "id": "f0e9d8c7-b6a5-4f3e-2d1c-0b9a8f7e6d5c",
                "associatedPartyId": "c1d2e3f4-a5b6-7c8d-9e0f-a1b2c3d4e5f6",
                "roles": ["SHAREHOLDER"],
                "status": "rfi",
                "level": "BASIC",
                "statusReason": {
                  "rejectLabels": ["DOCUMENT_QUALITY"],
                  "rejectSubLabels": ["DOCUMENT_BLURRY"]
                }
              }
            ],
            "metadata": {},
            "createdAt": "2024-03-20T15:30:00Z",
            "updatedAt": "2024-03-20T15:30:05Z"
          }
        }
        ```
      </Tab>
    </Tabs>
  </Tab>

  <Tab title="Account">
    <Tabs>
      <Tab title="provisioning">
        ```json theme={null}
        {
          "eventId": "d26c479f-f786-4be8-a694-c4da0e8393e7",
          "eventType": "account.provisioning",
          "data": {
            "id": "ce4c89f3-60f2-45a3-9de7-415a462dd95c",
            "customerId": "96fec1e2-78d9-4978-813d-674bba64020d",
            "status": "PROVISIONING",
            "currency": "USD",
            "createdAt": "2024-03-20T15:30:00Z",
            "updatedAt": "2024-03-20T15:30:05Z"
          }
        }
        ```
      </Tab>

      <Tab title="rfi">
        ```json theme={null}
        {
          "eventId": "550e8400-e29b-41d4-a716-446655440000",
          "eventType": "account.rfi",
          "data": {
            "id": "ce4c89f3-60f2-45a3-9de7-415a462dd95c",
            "customerId": "96fec1e2-78d9-4978-813d-674bba64020d",
            "status": "RFI",
            "currency": "USD",
            "createdAt": "2024-03-20T15:30:00Z",
            "updatedAt": "2024-03-20T15:30:05Z"
          }
        }
        ```
      </Tab>

      <Tab title="active">
        ```json theme={null}
        {
          "eventId": "81d783f1-3675-4f3a-92d1-e4f5af66cca3",
          "eventType": "account.active",
          "data": {
            "id": "d06d342c-629a-4d99-a26f-34e8ea528403",
            "customerId": "768d6aac-627e-44e8-aabd-d51d10f59c02",
            "status": "ACTIVE",
            "currency": "EUR",
            "createdAt": "2024-03-20T15:30:00Z",
            "updatedAt": "2024-03-20T15:30:05Z"
          }
        }
        ```
      </Tab>

      <Tab title="closed">
        ```json theme={null}
        {
          "eventId": "550e8400-e29b-41d4-a716-446655440000",
          "eventType": "account.closed",
          "data": {
            "id": "d06d342c-629a-4d99-a26f-34e8ea528403",
            "customerId": "768d6aac-627e-44e8-aabd-d51d10f59c02",
            "status": "CLOSED",
            "currency": "EUR",
            "createdAt": "2024-03-20T15:30:00Z",
            "updatedAt": "2024-03-20T15:30:05Z"
          }
        }
        ```
      </Tab>
    </Tabs>
  </Tab>

  <Tab title="Destination">
    <Tabs>
      <Tab title="under_verification">
        ```json theme={null}
        {
          "eventId": "550e8400-e29b-41d4-a716-446655440000",
          "eventType": "destinations.under_verification",
          "data": {
            "id": "ba123456-e89b-12d3-a456-426614174000",
            "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
            "status": "UNDER_VERIFICATION",
            "level": "BASIC",
            "createdAt": "2024-03-20T15:30:00Z",
            "updatedAt": "2024-03-20T15:30:05Z"
          }
        }
        ```
      </Tab>

      <Tab title="approved">
        ```json theme={null}
        {
          "eventId": "550e8400-e29b-41d4-a716-446655440000",
          "eventType": "destinations.approved",
          "data": {
            "id": "ba123456-e89b-12d3-a456-426614174000",
            "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
            "status": "APPROVED",
            "level": "BASIC",
            "createdAt": "2024-03-20T15:30:00Z",
            "updatedAt": "2024-03-20T15:30:05Z"
          }
        }
        ```
      </Tab>

      <Tab title="final_rejection">
        ```json theme={null}
        {
          "eventId": "550e8400-e29b-41d4-a716-446655440000",
          "eventType": "destinations.final_rejection",
          "data": {
            "id": "ba123456-e89b-12d3-a456-426614174000",
            "customerId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
            "status": "FINAL_REJECTION",
            "level": "BASIC",
            "createdAt": "2024-03-20T15:30:00Z",
            "updatedAt": "2024-03-20T15:30:05Z"
          }
        }
        ```
      </Tab>
    </Tabs>
  </Tab>
</Tabs>

## Verifying webhook signatures

Each webhook request includes three headers for signature verification:

| Header              | Description                                                            |
| :------------------ | :--------------------------------------------------------------------- |
| `webhook-id`        | Unique message identifier                                              |
| `webhook-timestamp` | Unix timestamp (seconds) of when the message was sent                  |
| `webhook-signature` | Base64-encoded signature(s), space-delimited and prefixed with version |

To verify a webhook signature, you construct the signed content, compute the expected signature, and compare it with the header value.

The signed content is created by concatenating the `webhook-id`, `webhook-timestamp`, and request body, separated by dots:

```
signed_content = "${webhook_id}.${webhook_timestamp}.${body}"
```

The signature is computed as a HMAC-SHA256 hash of the signed content using your webhook secret (base64-decoded) as the key, then base64-encoded.

<Note>
  Your webhook signing secret starts with `whsec_`. You must strip this prefix and base64-decode the remainder before using it as the HMAC key.
</Note>

<Tip>
  When you rotate your signing secret, Lumx continues signing messages with both the old and new secrets for 24 hours. This means the `webhook-signature` header may contain multiple signatures (e.g., `v1,<old> v1,<new>`). Your verification code should accept any valid signature from the list, which the examples below already handle.
</Tip>

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    import { createHmac, timingSafeEqual } from "crypto";

    function verifyWebhook(
      body: string,
      headers: Record<string, string>,
      secret: string
    ): boolean {
      const msgId = headers["webhook-id"];
      const timestamp = headers["webhook-timestamp"];
      const signature = headers["webhook-signature"];

      if (!msgId || !timestamp || !signature) {
        return false;
      }

      // Reject messages older than 5 minutes to prevent replay attacks
      const now = Math.floor(Date.now() / 1000);
      if (Math.abs(now - parseInt(timestamp)) > 300) {
        return false;
      }

      // Strip the whsec_ prefix and decode the secret
      const secretBytes = Buffer.from(secret.replace("whsec_", ""), "base64");

      // Compute the expected signature
      const signedContent = `${msgId}.${timestamp}.${body}`;
      const expectedSignature = createHmac("sha256", secretBytes)
        .update(signedContent)
        .digest("base64");

      // Compare against all provided signatures (there may be multiple)
      const signaturesV1 = signature
        .split(" ")
        .filter((s) => s.startsWith("v1,"))
        .map((s) => s.substring(3));

      return signaturesV1.some((sig) =>
        timingSafeEqual(Buffer.from(sig), Buffer.from(expectedSignature))
      );
    }
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    const { createHmac, timingSafeEqual } = require("crypto");

    function verifyWebhook(body, headers, secret) {
      const msgId = headers["webhook-id"];
      const timestamp = headers["webhook-timestamp"];
      const signature = headers["webhook-signature"];

      if (!msgId || !timestamp || !signature) {
        return false;
      }

      // Reject messages older than 5 minutes to prevent replay attacks
      const now = Math.floor(Date.now() / 1000);
      if (Math.abs(now - parseInt(timestamp)) > 300) {
        return false;
      }

      // Strip the whsec_ prefix and decode the secret
      const secretBytes = Buffer.from(secret.replace("whsec_", ""), "base64");

      // Compute the expected signature
      const signedContent = `${msgId}.${timestamp}.${body}`;
      const expectedSignature = createHmac("sha256", secretBytes)
        .update(signedContent)
        .digest("base64");

      // Compare against all provided signatures (there may be multiple)
      const signaturesV1 = signature
        .split(" ")
        .filter((s) => s.startsWith("v1,"))
        .map((s) => s.substring(3));

      return signaturesV1.some((sig) =>
        timingSafeEqual(Buffer.from(sig), Buffer.from(expectedSignature))
      );
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import base64
    import hashlib
    import hmac
    import time


    def verify_webhook(
        body: str, headers: dict[str, str], secret: str
    ) -> bool:
        msg_id = headers.get("webhook-id")
        timestamp = headers.get("webhook-timestamp")
        signature = headers.get("webhook-signature")

        if not msg_id or not timestamp or not signature:
            return False

        # Reject messages older than 5 minutes to prevent replay attacks
        now = int(time.time())
        if abs(now - int(timestamp)) > 300:
            return False

        # Strip the whsec_ prefix and decode the secret
        secret_bytes = base64.b64decode(secret.removeprefix("whsec_"))

        # Compute the expected signature
        signed_content = f"{msg_id}.{timestamp}.{body}"
        expected_signature = base64.b64encode(
            hmac.new(
                secret_bytes, signed_content.encode(), hashlib.sha256
            ).digest()
        ).decode()

        # Compare against all provided signatures (there may be multiple)
        signatures_v1 = [
            s.removeprefix("v1,")
            for s in signature.split(" ")
            if s.startswith("v1,")
        ]

        return any(
            hmac.compare_digest(sig, expected_signature)
            for sig in signatures_v1
        )
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    package webhook

    import (
    	"crypto/hmac"
    	"crypto/sha256"
    	"encoding/base64"
    	"fmt"
    	"math"
    	"net/http"
    	"strconv"
    	"strings"
    	"time"
    )

    func VerifyWebhook(body string, headers http.Header, secret string) bool {
    	msgID := headers.Get("webhook-id")
    	timestamp := headers.Get("webhook-timestamp")
    	signature := headers.Get("webhook-signature")

    	if msgID == "" || timestamp == "" || signature == "" {
    		return false
    	}

    	// Reject messages older than 5 minutes to prevent replay attacks
    	ts, err := strconv.ParseInt(timestamp, 10, 64)
    	if err != nil {
    		return false
    	}
    	now := time.Now().Unix()
    	if math.Abs(float64(now-ts)) > 300 {
    		return false
    	}

    	// Strip the whsec_ prefix and decode the secret
    	secretBytes, err := base64.StdEncoding.DecodeString(
    		strings.TrimPrefix(secret, "whsec_"),
    	)
    	if err != nil {
    		return false
    	}

    	// Compute the expected signature
    	signedContent := fmt.Sprintf("%s.%s.%s", msgID, timestamp, body)
    	mac := hmac.New(sha256.New, secretBytes)
    	mac.Write([]byte(signedContent))
    	expectedSignature := base64.StdEncoding.EncodeToString(mac.Sum(nil))

    	// Compare against all provided signatures (there may be multiple)
    	for _, sig := range strings.Split(signature, " ") {
    		if strings.HasPrefix(sig, "v1,") {
    			if hmac.Equal(
    				[]byte(strings.TrimPrefix(sig, "v1,")),
    				[]byte(expectedSignature),
    			) {
    				return true
    			}
    		}
    	}

    	return false
    }
    ```
  </Tab>
</Tabs>

## Retries

If your endpoint does not return a `2xx` response, Lumx retries the delivery with exponential backoff:

| Attempt | Delay       |
| :------ | :---------- |
| 1       | Immediately |
| 2       | 5 seconds   |
| 3       | 5 minutes   |
| 4       | 30 minutes  |
| 5       | 2 hours     |
| 6       | 5 hours     |
| 7       | 10 hours    |
| 8       | 10 hours    |

After all retry attempts are exhausted, Lumx marks the message as failed. You can manually retry failed messages from the [Dashboard](https://dashboard.lumx.io).

<Tip>
  Use the `webhook-id` header to deduplicate events in case your endpoint receives the same event more than once.
</Tip>

## IP allowlisting

If your infrastructure requires allowlisting specific IPs, add the following addresses. These IPs are shared across both sandbox and production environments:

```
44.214.29.156/32
3.82.0.0/32
100.56.2.161/32
```

## Basic authentication

If your endpoint requires HTTP Basic authentication, include the credentials directly in the endpoint URL:

```
https://username:password@your-domain.com/webhook
```

Lumx extracts the credentials and sends them in the `Authorization` header on every webhook request:

```
Authorization: Basic <base64(username:password)>
```
