Create a WavyNode Integration
Integrations are a powerful way to connect your application with WavyNode. By creating an integration, you can receive real-time notifications about your users' on-chain activity and provide WavyNode with the necessary data to monitor their wallets.
This guide will walk you through the process of creating a new integration from scratch.
Getting Started
There are two ways to create a new integration:
-
Using the template: We provide a template repository that you can use as a starting point. The template is a Nitro application with all the required endpoints and authentication already set up.
-
From scratch: You can create a new integration from scratch using any framework or language you prefer. This guide will focus on this approach.
@wavynode/utils Package
We provide an npm package with utilities to help you with the integration process. You can install it using your favorite package manager:
This package includes the validateSignature function, which you will need to authenticate requests from WavyNode.
Authentication
WavyNode uses a simple but secure authentication method to verify that incoming requests are from us. All requests sent to your integration will include the following headers:
x-wavynode-hmac: A base64 encoded HMAC-SHA256 signature of the request body.x-wavynode-timestamp: The timestamp of the request in milliseconds.
To verify the signature, you can use the validateSignature function from the @wavynode/utils package. This function takes the following parameters:
method: The HTTP method of the request.path: The path of the request.body: The request body.timestamp: The timestamp from thex-wavynode-timestampheader.secret: Your integration's secret. You can find this in your project's settings in the WavyNode dashboard.timeTolerance: The time tolerance in milliseconds. This is to prevent replay attacks. We recommend a value of300000(5 minutes).signature: The signature from thex-wavynode-hmacheader.
Here is an example of how to use the validateSignature function:
import { validateSignature } from '@wavynode/utils';
const isValid = validateSignature({
method: 'POST',
path: '/webhook',
body: req.body,
timestamp: parseInt(req.headers['x-wavynode-timestamp']),
secret: process.env.SECRET,
timeTolerance: 300000,
signature: req.headers['x-wavynode-hmac']
});
if (!isValid) {
// The request is not from WavyNode
// or has been tampered with.
// You should reject the request.
}
Manual Authentication
If you are not using the @wavynode/utils package, you can implement the authentication logic yourself. Here is how to create the canonical string and the HMAC signature:
-
Create the canonical string: The canonical string is a concatenation of the following, separated by
:::- The uppercase HTTP method (
GET,POST, etc.). - The lowercase request path (e.g.,
/webhook). - The stringified request body with its keys sorted alphabetically, or an empty object
{}if no body is provided in the request. - The timestamp from the
x-wavynode-timestampheader.
Here is an example of a valid canonical string:
- The uppercase HTTP method (
-
Create the HMAC signature: The signature is a
sha256HMAC of the canonical string, using your integration's secret as the key. The result should be base64 encoded. -
Compare the signatures: Compare the signature you created with the one from the
x-wavynode-hmacheader. If they are identical, the request is authentic.
Endpoints
Your integration must expose the following endpoints:
GET /users/:userId
This endpoint is used by WavyNode to retrieve information about a specific user. The :userId parameter will be the user's ID in your system.
Your endpoint should return a JSON object with the following properties:
givenName: The individual's given name(s). This can include multiple names, such as "Maria Guadalupe".maternalSurname: The individual's maternal surname, which is the last name from their mother's side.paternalSurname: The individual's paternal surname, which is the last name from their father's side.birthdate: Date of birth in 'YYYY-MM-DD' format (ISO 8601).nationality: ISO 3166-1 alpha-2 country code.rfc: (Optional) Mexican RFC (Registro Federal de Contribuyentes).curp: (Optional) Mexican CURP (Clave Única de Registro de Población).
Here is an example of a valid response:
{
"givenName": "Maria Guadalupe",
"maternalSurname": "Lopez",
"paternalSurname": "Perez",
"birthdate": "1990-01-15",
"nationality": "MX",
"rfc": "LOPM900115ABC",
"curp": "LOPM900115MDFABC12"
}
POST /webhook
This endpoint is used by WavyNode to send you real-time notifications. The request body will be a JSON object with the following properties:
type: The type of the notification. This can benotificationorerror.data: The notification payload.
Notification Payload
If the type is notification, the data object will contain the following properties:
id: The notification's ID.projectId: The ID of the project this notification belongs to.chainId: The ID of the chain where the transaction occurred.address: An object containing information about the address involved in the transaction.id: The address' ID.address: The address.description: The address' description.
txHash: The transaction hash.timestamp: The timestamp of the transaction.amount: An object containing the transaction's amount.value: The amount in the token's smallest unit.usd: The amount in USD.
token: An object containing information about the token used in the transaction.name: The token's name.symbol: The token's symbol.decimals: The token's decimals.address: The token's address.
inflictedLaws: An array of objects containing information about the laws inflicted by the transaction.name: The law's name.description: The law's description.source: The law's source.risk: The law's risk. This can bewarnorillegal.country: The law's country.countryCode: The law's country code.
Error Payload
If the type is error, the data object will be a string containing the error message.
Here is an example of a valid notification payload:
{
"type": "notification",
"data": {
"id": 1,
"projectId": 1,
"chainId": 42161,
"address": {
"id": 543,
"address": "0xyour-address-involved",
"description": "Your address' description"
},
"txHash": "some-tx-hash",
"timestamp": "2025-08-20T05:10:57.228Z",
"amount": {
"value": 1000000000000000000,
"usd": 3000
},
"token": {
"name": "Ethereum",
"symbol": "ETH",
"decimals": 18,
"address": null
},
"inflictedLaws": [
{
"name": "The name of the law inflicted",
"description": "Description of the law",
"source": "Source of the law",
"risk": "warn",
"country": "mexico",
"countryCode": "MX"
}
]
}
}