npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@findmore.eu/hs-utils

v1.4.1

Published

Utility functions for HubSpot and Azure Functions related to HubSpot API

Downloads

243

Readme

@findmore.eu/hs-utils

Utility functions for HubSpot and Azure Functions related to HubSpot API.

Installation

npm install @findmore.eu/hs-utils

Features

  • Signature Verification: Verify HubSpot webhook signatures (both standard HMAC-SHA256 and custom implementations)
  • Signature Generation: Generate custom signatures for request validation with HMAC-SHA256 or Base64 fallback
  • Azure Logging: Simple Azure-friendly logger for Azure Functions with file logging and auto-cleanup
  • Centralized Logger: Send logs to a centralized logging service with HMAC-SHA256 signature verification
  • Simple Cache: In-memory and file-based cache with TTL support for Azure Functions
  • TypeScript Support: Full TypeScript type definitions included

Usage

Verify HubSpot Signature

import { verifyHubspotSignature } from '@findmore.eu/hs-utils';

const request = {
  url: '/webhook',
  method: 'POST',
  body: { data: 'example' },
  headers: {
    'x-hubspot-signature-v3': 'signature_here',
    'x-hubspot-request-timestamp': '1234567890'
  },
  hostname: 'your-domain.com'
};

const isValid = verifyHubspotSignature(request, {
  clientSecret: 'your-client-secret' // or use process.env.CLIENT_SECRET
});

Verify Custom Signature

import { verifyCustomSignature } from '@findmore.eu/hs-utils';

const request = {
  url: 'https://your-domain.com/webhook',
  method: 'POST',
  headers: {
    get: (name: string) => {
      // Return header value
    }
  }
};

const bodyText = JSON.stringify({ data: 'example' });

const isValid = verifyCustomSignature(request, bodyText, undefined, undefined, {
  clientSecret: 'your-client-secret'
});

Generate Custom Signature

import { generateCustomSignature } from '@findmore.eu/hs-utils';

// Secure: Using HMAC-SHA256 with secret (recommended)
const secureSignature = generateCustomSignature(
  'https://your-domain.com/webhook',
  'POST',
  '1234567890',
  { data: 'example' },
  { clientSecret: 'your-secret-key' }
);

// Fallback: Base64 encoding without secret (NOT secure, for backward compatibility only)
const insecureSignature = generateCustomSignature(
  'https://your-domain.com/webhook',
  'POST',
  '1234567890',
  { data: 'example' }
);

Azure Logger

import { SimpleAzureLogger } from '@findmore.eu/hs-utils';

// Create logger instance (console only)
const logger = new SimpleAzureLogger('my-function-name');

// Or with file logging and auto-cleanup (delete files older than 7 days)
const loggerWithCleanup = new SimpleAzureLogger('my-function-name', true, 7);

// Generate a request ID for tracking
const requestId = logger.generateRequestId();

// Log levels with appropriate use cases:

// INFO - Normal operation milestone
logger.info('Processing request', { userId: '123' }, requestId);

// SUCCESS - Operation completed successfully (with optional duration in ms)
logger.success('Request completed', 1234, { result: 'success' }, requestId);

// ERROR - Something failed (with error object)
logger.error('Request failed', new Error('Something went wrong'), { context: 'details' }, requestId);

// WARN - Potential issue, but continuing
logger.warn('API rate limit approaching', { remaining: 10 }, requestId);

// DEBUG - Detailed debugging information
logger.debug('Cache lookup', { key: 'user:123', hit: true }, requestId);

// Advanced: Custom log with any fields
logger.log({
    level: 'INFO',
    message: 'Custom log entry',
    customField1: 'value1',
    customField2: 42,
    requestId
});

// Measure execution time of async operations
const result = await logger.measureTime(
    'fetchHubSpotData',
    async () => {
        return await fetchData();
    },
    requestId
);

Send Log to Centralized Logger

Send logs to a centralized logging service with HMAC-SHA256 signature verification.

import { SendLogToCentralizedLogger } from '@findmore.eu/hs-utils';

const logEntry = {
  appName: 'my-app',
  level: 'INFO',
  message: 'Processing request',
  data: { userId: '123', action: 'login' },
  requestId: 'req-123'
};

try {
  const response = await SendLogToCentralizedLogger(
    logEntry,
    'https://your-centralized-logger.com/api/logs',
    'your-log-secret-key'
  );
  console.log('Log sent successfully:', response);
} catch (error) {
  console.error('Failed to send log:', error);
}

Parameters:

  • logEntry: Object containing log data
    • appName (string): Application name
    • level (string): Log level (INFO, WARN, ERROR, DEBUG, SUCCESS)
    • message (string): Log message
    • data (object, optional): Additional data
    • error (object, optional): Error details
    • Any other custom fields
  • apiUrl (string): URL of the centralized logger API endpoint
  • logSecret (string): Secret key for HMAC-SHA256 signature generation

Returns: Promise<Object> - Response from the centralized logger API

Security:

  • Automatically generates HMAC-SHA256 signature for request authentication
  • Includes timestamp to prevent replay attacks
  • Uses x-hubspot-signature-v3 and x-hubspot-request-timestamp headers

Simple Cache

A persistent cache system that works across Azure Function invocations with both in-memory and file-based storage.

import { SimpleCache } from '@findmore.eu/hs-utils';

// Create cache instance
// Default: 24 hours TTL, 'function_cache.json', 'windows'
const cache = new SimpleCache();

// Custom configuration
const customCache = new SimpleCache(
    12 * 60 * 60 * 1000,         // 12 hours TTL (in milliseconds)
    'my_custom_cache.json',      // Custom filename
    'linux'                       // OS: 'linux' or 'windows'
);

// Store data with default TTL
cache.set('user:123', { name: 'John Doe', email: '[email protected]' });

// Store data with custom TTL (1 hour)
cache.set('temporary:data', { value: 'temp' }, 60 * 60 * 1000);

// Retrieve data (returns null if expired or not found)
const user = cache.get<{ name: string; email: string }>('user:123');
if (user) {
    console.log(user.name); // 'John Doe'
}

// Get all cache entries
const allEntries = cache.getAll();

// Get cache size
const size = cache.length();

// Practical example: Cache HubSpot schema
const schemaKey = `schema:${objectType}`;
let schema = cache.get(schemaKey);

if (!schema) {
    // Not in cache, fetch from API
    schema = await fetchHubSpotSchema(objectType);
    
    // Store in cache for 24 hours
    cache.set(schemaKey, schema);
}

return schema;

Key Features:

  • Dual Storage: In-memory (fast) + File-based (persistent across invocations)
  • TTL Support: Automatic expiration of cached items
  • Azure-Optimized: Handles temporary directories for both Linux and Windows
  • Built-in Logging: Uses SimpleAzureLogger with 7-day auto-cleanup
  • Type-Safe: Full TypeScript support with generics

Cache Locations:

  • Windows: D:\local\Temp\function_cache.json
  • Linux: /tmp/function_cache.json

API Reference

verifyHubspotSignature(request, options?)

Verifies HubSpot webhook signature using HMAC-SHA256.

Parameters:

  • request: Object containing url, method, body, headers, and hostname
  • options: Optional object with clientSecret (falls back to process.env.CLIENT_SECRET)

Returns: boolean | undefined

verifyCustomSignature(request, bodyText, logger?, requestId?, options?)

Verifies custom signature with optional Azure logging.

Parameters:

  • request: Object containing url, method, and headers
  • bodyText: Request body as string
  • logger: Optional SimpleAzureLogger instance
  • requestId: Optional request ID for logging
  • options: Optional object with clientSecret

Returns: boolean

generateCustomSignature(url, method, timestamp, body?, options?)

Generates a signature for request validation.

Parameters:

  • url: Full request URL
  • method: HTTP method
  • timestamp: Request timestamp as string
  • body: Optional request body object
  • options: Optional object with clientSecret (or use process.env.CLIENT_SECRET)

Returns: string

Behavior:

  • With clientSecret: Uses HMAC-SHA256 (cryptographically secure)
  • Without clientSecret: Falls back to Base64 encoding (NOT secure, for backward compatibility only)

SendLogToCentralizedLogger(logEntry, apiUrl, logSecret)

Sends a log entry to a centralized logging service with HMAC-SHA256 signature authentication.

Parameters:

  • logEntry: Object containing log data with the following structure:
    • appName (string): Name of the application sending the log
    • level (string): Log level - one of: 'INFO', 'WARN', 'ERROR', 'DEBUG', 'SUCCESS'
    • message (string): The log message
    • data (object, optional): Additional context data
    • error (object, optional): Error details if applicable
    • Any other custom fields
  • apiUrl (string): Full URL of the centralized logger API endpoint
  • logSecret (string): Secret key used for HMAC-SHA256 signature generation

Returns: Promise<Object> - Response from the centralized logger API

Throws: Error if the request fails or the API returns an error

Implementation Details:

  • Generates signature using: HMAC-SHA256(METHOD + URL + BODY + TIMESTAMP, logSecret)
  • Signature sent in x-hubspot-signature-v3 header
  • Timestamp (in milliseconds) sent in x-hubspot-request-timestamp header
  • Request body is JSON-encoded log entry

SimpleAzureLogger(logPrefix?, enableFileLogging?, deleteOlderThanDays?)

Creates a new logger instance for Azure Functions.

Constructor Parameters:

  • logPrefix (string, optional): Name/prefix for logs (e.g., function name). Default: 'azure-function'
  • enableFileLogging (boolean, optional): Whether to attempt file logging. Default: false
  • deleteOlderThanDays (number, optional): Delete log files older than X days. Only works if enableFileLogging is true

Methods:

generateRequestId(): string

Generates a unique request ID for tracking related log entries.

info(message, data?, requestId?): void

Logs an informational message.

  • message: The log message
  • data: Optional data object with additional context
  • requestId: Optional request ID for tracking

success(message, duration?, data?, requestId?): void

Logs a success message with optional execution duration.

  • message: The log message
  • duration: Optional execution time in milliseconds
  • data: Optional data object with additional context
  • requestId: Optional request ID for tracking

error(message, error?, data?, requestId?): void

Logs an error message.

  • message: The log message
  • error: Optional error object or value
  • data: Optional data object with additional context
  • requestId: Optional request ID for tracking

warn(message, data?, requestId?): void

Logs a warning message.

  • message: The log message
  • data: Optional data object with additional context
  • requestId: Optional request ID for tracking

debug(message, data?, requestId?): void

Logs a debug message.

  • message: The log message
  • data: Optional data object with additional context
  • requestId: Optional request ID for tracking

log(entry): void

Main log method with full flexibility. Accepts any log entry with custom fields.

  • entry: Object with level, message, and any additional custom fields

measureTime<T>(operation, fn, requestId?): Promise<T>

Measures execution time of an async function and logs the result.

  • operation: Name of the operation being measured
  • fn: Async function to execute and measure
  • requestId: Optional request ID for tracking
  • Returns: The result of the async function

getBufferedLogs(): string[]

Returns the last 100 log entries from memory.

clearBuffer(): void

Clears the in-memory log buffer.

exportBufferAsJson(): string

Exports buffered logs as JSON string.

tryReadLogs(date?): string | null

Attempts to read logs from file (if file logging is enabled).

  • date: Optional date in YYYY-MM-DD format. Default: today

tryListLogFiles(): string[]

Lists available log files (most recent first).

getLogFileInfo(): object

Returns information about log file location and status.

getLoggerStatus(): object

Returns a summary of the logger status including buffer size and available log files.

SimpleCache(cacheTtl?, filename?, osSystem?)

Creates a persistent cache instance for Azure Functions.

Constructor Parameters:

  • cacheTtl (number, optional): Default TTL for cached items in milliseconds. Default: 24 * 60 * 60 * 1000 (24 hours)
  • filename (string, optional): Cache file name. Default: 'function_cache.json'
  • osSystem ('linux' | 'windows', optional): Operating system for temp directory. Default: 'windows'

Methods:

set(key, value, ttlMs?): void

Stores a value in cache with optional TTL.

  • key: String key to identify the cached item
  • value: Any serializable value to cache
  • ttlMs: Optional TTL in milliseconds (overrides default TTL)

get<T>(key): T | null

Retrieves a value from cache.

  • key: String key of the cached item
  • Returns: The cached value or null if not found or expired
  • Supports TypeScript generics for type safety

getAll(): Map<string, { data: any, expiry: number }>

Returns all cache entries with their data and expiry times.

length(): number

Returns the number of items currently in cache.

How it works:

  1. In-Memory Priority: Checks in-memory cache first for fast access
  2. File Fallback: If not in memory, loads from file (persists across invocations)
  3. Auto-Load: Automatically loads valid entries from file on instantiation
  4. Auto-Save: Automatically saves to file when using set()
  5. Auto-Expire: Expired items are automatically filtered out
  6. Logging: Built-in logging with SimpleAzureLogger (7-day auto-cleanup)

Security Notes

Recommended: Use HMAC-SHA256 with Secret

Always provide a clientSecret to use secure HMAC-SHA256 signing:

const signature = generateCustomSignature(url, method, timestamp, body, {
  clientSecret: 'your-secret-key'
});

Fallback Mode (Insecure)

Without a clientSecret, the function falls back to Base64 encoding:

  • Not cryptographically secure
  • Anyone can decode and recreate signatures
  • Only use for backward compatibility with existing systems
  • Should be migrated to HMAC-SHA256 as soon as possible

Why HMAC-SHA256 is Secure

  1. Secret Key Required: Only parties with the secret can generate valid signatures
  2. One-Way Function: Cannot be reversed to get the original data
  3. Collision Resistant: Extremely difficult to find two inputs with the same hash
  4. Industry Standard: Used by HubSpot, AWS, GitHub, and other major platforms

License

CC-BY-NC-ND-4.0

Author

findmore.eu [email protected]