Skip to main content

hookah-sdk

TypeScript SDK for interacting with Hookah's webhook and event monitoring system for Radix DLT.

npm package

Installation

npm install hookah-sdk
yarn add hookah-sdk
pnpm add hookah-sdk

Quick Start

import { createHookahSdk } from 'hookah-sdk';

const keypair = new PrivateKey.Ed25519(PRIVATE_KEY);

const sdk = createHookahSdk({
publicKey: keypair.publicKey(),
personaLabel: 'ED',
signer: async (message) => keypair.signToSignature(message).hex(),
});

// Authenticate
await sdk.auth();

// Get typed tRPC client for API interactions
const client = await sdk.getTrpcClient();

// Use the client to manage webhooks
await client.webhook.create.mutate({
url: 'https://your-webhook-endpoint.com',
});

Usage Examples

Initialization & Authentication

First, import the necessary function and initialize the SDK. You'll need a Radix compatible keypair for signing authentication challenges.

import { createHookahSdk } from 'hookah-sdk';
import { createEd25519KeyPair } from 'radix-web3.js';

// Replace with your actual private key
const privateKeyHex = 'YOUR_ED25519_PRIVATE_KEY_HEX';
const keypair = createEd25519KeyPair(privateKeyHex);

const sdk = createHookahSdk({
publicKey: keypair.publicKey(),
personaLabel: 'ED', // A label for your application/persona
signer: async (message) => keypair.signToSignature(message).hex(),
});

// Authenticate to get a session
try {
await sdk.auth();
console.log('Authentication successful!');

// Get the type-safe tRPC client for further interactions
const client = await sdk.getTrpcClient();

// Verify session (optional)
const { session } = await client.auth.getSession.query();
console.log('User ID:', session.userId);
} catch (error) {
console.error('Authentication failed:', error);
}

Webhook Management

Once authenticated, you can manage webhooks using the tRPC client.

// Assuming 'client' is the authenticated tRPC client from the previous step

const webhookUrl = 'https://your-server.com/webhook-handler';
const webhookName = 'My Transaction Monitor';

try {
// Create a new webhook
const createdWebhook = await client.webhook.create.mutate({
url: webhookUrl,
name: webhookName,
// Optional: Add custom headers
// headerKey: 'X-API-Key',
// headerValue: 'your-secret-key'
});
const webhookId = createdWebhook.id;
console.log('Webhook created with ID:', webhookId);

// Get all webhooks for the user
const webhooks = await client.webhook.getByUserId.query();
console.log('User webhooks:', webhooks);

// Get a specific webhook by ID
const specificWebhook = await client.webhook.getById.query({ id: webhookId });
console.log('Fetched webhook:', specificWebhook);

// Delete the webhook (uncomment to run)
/*
await client.webhook.remove.mutate({ id: webhookId });
console.log('Webhook deleted.');
const remainingWebhooks = await client.webhook.getByUserId.query();
console.log('Remaining webhooks:', remainingWebhooks);
*/
} catch (error) {
console.error('Webhook management error:', error);
}

Trigger Management

Triggers define the conditions under which a webhook fires.

// Assuming 'client' and 'webhookId' are available

// Example: Trigger on a specific event from a specific component address
const emitterAddress = 'component_tdx_a_1q...'; // Replace with actual address
const eventName = 'DepositEvent'; // Or any other event name

try {
// Create a simple trigger
const simpleTrigger = await client.trigger.create.mutate({
webhookId: webhookId,
emitterAddress: emitterAddress,
eventName: eventName,
status: 'active', // 'active' or 'inactive'
});
const simpleTriggerId = simpleTrigger.id;
console.log('Simple trigger created:', simpleTriggerId);

// Create a trigger with conditions
const conditionalTrigger = await client.trigger.create.mutate({
webhookId: webhookId,
emitterAddress: 'account_tdx_b_1q...', // Replace with account address
eventName: 'DepositEvent',
status: 'active',
conditions: [
{
id: 'cond1', // Unique ID for the condition within the trigger
field: 'amount', // Field from the event data to check
comparator: 'gte_n', // Comparator (e.g., 'eq_n', 'gte_n', 'lt_s', 'contains_s')
value: '1000', // Value to compare against (as string)
},
{
id: 'cond2',
field: 'resourceAddress', // Check the resource address
comparator: 'eq_s',
value: 'resource_tdx_c_1q...', // Replace with XRD or other resource address
},
],
});
console.log('Conditional trigger created:', conditionalTrigger.id);

// Get all triggers for the user
const triggers = await client.trigger.getByUserId.query();
console.log('User triggers:', triggers);

// Delete triggers (uncomment to run)
/*
for (const trigger of triggers) {
await client.trigger.remove.mutate({ id: trigger.id });
console.log('Deleted trigger:', trigger.id);
}
*/
} catch (error) {
console.error('Trigger management error:', error);
}

Viewing Logs

You can query the execution logs for your webhooks.

// Assuming 'client' and 'webhookId' are available

try {
// Get all logs for a specific webhook
const logs = await client.log.getByWebhookId.query({ webhookId: webhookId });
console.log(`Logs for webhook ${webhookId}:`);
logs.forEach((log) => {
console.log(
`- ID: ${log.id}, Status: ${log.status}, TxID: ${log.transactionId}, Time: ${log.executedAt}`,
);
// Log data contains the event payload sent to the webhook
// console.log(' Data:', log.data);
});

// Get all logs for the user
const allUserLogs = await client.log.getByUserId.query();
console.log('All user logs:', allUserLogs.length);
} catch (error) {
console.error('Error fetching logs:', error);
}

Trigger Conditions

Trigger conditions allow you to filter events based on the specific data they contain. When you create a trigger, you can optionally add an array of conditions. The webhook will only fire if all conditions associated with the trigger evaluate to true for a given event.

Each condition object has the following structure:

  • id: A unique identifier for the condition within the trigger (e.g., 'cond1').
  • field: A string specifying the path to the data field within the event payload you want to check.
  • comparator: A string defining the comparison operation to perform. The available comparators depend on the expected data type of the field:
    • Numbers (_n):
      • 'eq_n': Equal to
      • 'neq_n': Not equal to
      • 'gt_n': Greater than
      • 'lt_n': Less than
      • 'gte_n': Greater than or equal to
      • 'lte_n': Less than or equal to
    • Strings (_s):
      • 'eq_s': Equal to
      • 'neq_s': Not equal to
      • 'c_s': Contains
      • 'nc_s': Does not contain
      • 'sw_s': Starts with
      • 'ew_s': Ends with
    • Booleans (_b):
      • 'eq_b': Equal to (true/false)
      • 'neq_b': Not equal to (true/false)
  • value: A string representing the value to compare the field against. Important: Even for numeric or boolean comparisons, provide the value as a string (e.g., '1000', 'true'). The system will handle the type conversion based on the comparator used.

See the examples below for practical usage.

Event data tools:

Trigger conditions schemas:

Conditions are an array of objects that define the conditions for the trigger.

const conditionalTrigger = await client.trigger.create.mutate({
webhookId,
emitterAddress: 'account_tdx_b_1q...', // Replace with account address
eventName: 'DepositEvent',
status: 'active',
conditions: [TRIGGER_CONDITIONS],
});

Trigger conditions examples

// Example 1: Trigger on a specific event from a specific component address
[{
id: 'cond1',
field: 'amount',
comparator: 'gte_n',
value: '1000',
}]

// Example 2: String value is equal to 'XRD'

[{
id: 'cond2',
field: 'x_token_address',
comparator: 'eq_s',
value: XRD_TOKEN_ADDRESS,
},]

// Example 3: String value contains 'XRD'
[{
id: 'cond3',
field: 'x_token_address',
comparator: 'contains_s',
value: 'XRD',
}]

// Example 4: String value starts with 'XRD'
[{
id: 'cond4',
field: 'x_token_address',
comparator: 'sw_s',
value: 'XRD',
},]

// Example 5: String value ends with 'XRD'
[{
id: 'cond5',
field: 'x_token_address',
comparator: 'ew_s',
value: 'XRD',
}]

Zod schemas for trigger conditions

import { z } from 'zod';

export const TriggerRuleV1Schema = z.object({
emitterAddress: z.string(),
eventName: z.string(),
});

export const TriggerDefinitionV1Schema = z.object({
version: z.literal(1),
rules: z.array(TriggerRuleV1Schema).length(1),
});

export const TriggerV1Schema = TriggerDefinitionV1Schema.extend({
id: z.string(),
});

export type TriggerV1 = z.infer<typeof TriggerV1Schema>;
export type TriggerRuleV1 = z.infer<typeof TriggerRuleV1Schema>;
export type TriggerDefinitionV1 = z.infer<typeof TriggerDefinitionV1Schema>;

const numberOperators = z.union([
z.literal('eq_n'), // equals
z.literal('neq_n'), // not equals
z.literal('gt_n'), // greater than
z.literal('lt_n'), // less than
z.literal('gte_n'), // greater than or equal to
z.literal('lte_n'), // less than or equal to
]);

const stringOperators = z.union([
z.literal('eq_s'), // equals
z.literal('neq_s'), // not equals
z.literal('c_s'), // contains
z.literal('nc_s'), // not contains
z.literal('sw_s'), // starts with
z.literal('ew_s'), // ends with
]);

const booleanOperators = z.union([
z.literal('eq_b'), // equals
z.literal('neq_b'), // not equals
]);

export const ComparatorSchema = z.union([
numberOperators,
stringOperators,
booleanOperators,
]);

export const TriggerConditionSchema = z.object({
id: z.string(),
field: z.string(),
comparator: ComparatorSchema,
value: z.string(),
});

export const TriggerRuleV2Schema = z.object({
emitterAddress: z.string(),
eventName: z.string(),
conditions: z.array(TriggerConditionSchema),
});

export const TriggerDefinitionV2Schema = z.object({
version: z.literal(2),
rules: z.array(TriggerRuleV2Schema).length(1),
});

export const TriggerDefinitionSchema = z.union([
TriggerDefinitionV1Schema,
TriggerDefinitionV2Schema,
]);

export type TriggerDefinition = z.infer<typeof TriggerDefinitionSchema>;

export const isV2 = (
definition: TriggerDefinition,
_rule: TriggerRuleV2 | TriggerRuleV1,
): _rule is TriggerRuleV2 => {
return definition.version === 2;
};

export const TriggerV2Schema = TriggerDefinitionV2Schema.extend({
id: z.string(),
});

export const TriggerSchema = z.discriminatedUnion('version', [
TriggerV1Schema,
TriggerV2Schema,
]);

export type Trigger = z.infer<typeof TriggerSchema>;
export type TriggerV2 = z.infer<typeof TriggerV2Schema>;
export type TriggerRuleV2 = z.infer<typeof TriggerRuleV2Schema>;
export type TriggerDefinitionV2 = z.infer<typeof TriggerDefinitionV2Schema>;
export type TriggerCondition = z.infer<typeof TriggerConditionSchema>;
export type Comparator = z.infer<typeof ComparatorSchema>;