@findmore.eu/hs-utils
v1.4.1
Published
Utility functions for HubSpot and Azure Functions related to HubSpot API
Downloads
243
Maintainers
Readme
@findmore.eu/hs-utils
Utility functions for HubSpot and Azure Functions related to HubSpot API.
Installation
npm install @findmore.eu/hs-utilsFeatures
- 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 dataappName(string): Application namelevel(string): Log level (INFO, WARN, ERROR, DEBUG, SUCCESS)message(string): Log messagedata(object, optional): Additional dataerror(object, optional): Error details- Any other custom fields
apiUrl(string): URL of the centralized logger API endpointlogSecret(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-v3andx-hubspot-request-timestampheaders
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 containingurl,method,body,headers, andhostnameoptions: Optional object withclientSecret(falls back toprocess.env.CLIENT_SECRET)
Returns: boolean | undefined
verifyCustomSignature(request, bodyText, logger?, requestId?, options?)
Verifies custom signature with optional Azure logging.
Parameters:
request: Object containingurl,method, andheadersbodyText: Request body as stringlogger: OptionalSimpleAzureLoggerinstancerequestId: Optional request ID for loggingoptions: Optional object withclientSecret
Returns: boolean
generateCustomSignature(url, method, timestamp, body?, options?)
Generates a signature for request validation.
Parameters:
url: Full request URLmethod: HTTP methodtimestamp: Request timestamp as stringbody: Optional request body objectoptions: Optional object withclientSecret(or useprocess.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 loglevel(string): Log level - one of:'INFO','WARN','ERROR','DEBUG','SUCCESS'message(string): The log messagedata(object, optional): Additional context dataerror(object, optional): Error details if applicable- Any other custom fields
apiUrl(string): Full URL of the centralized logger API endpointlogSecret(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-v3header - Timestamp (in milliseconds) sent in
x-hubspot-request-timestampheader - 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:falsedeleteOlderThanDays(number, optional): Delete log files older than X days. Only works ifenableFileLoggingistrue
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 messagedata: Optional data object with additional contextrequestId: Optional request ID for tracking
success(message, duration?, data?, requestId?): void
Logs a success message with optional execution duration.
message: The log messageduration: Optional execution time in millisecondsdata: Optional data object with additional contextrequestId: Optional request ID for tracking
error(message, error?, data?, requestId?): void
Logs an error message.
message: The log messageerror: Optional error object or valuedata: Optional data object with additional contextrequestId: Optional request ID for tracking
warn(message, data?, requestId?): void
Logs a warning message.
message: The log messagedata: Optional data object with additional contextrequestId: Optional request ID for tracking
debug(message, data?, requestId?): void
Logs a debug message.
message: The log messagedata: Optional data object with additional contextrequestId: Optional request ID for tracking
log(entry): void
Main log method with full flexibility. Accepts any log entry with custom fields.
entry: Object withlevel,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 measuredfn: Async function to execute and measurerequestId: 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 itemvalue: Any serializable value to cachettlMs: 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
nullif 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:
- In-Memory Priority: Checks in-memory cache first for fast access
- File Fallback: If not in memory, loads from file (persists across invocations)
- Auto-Load: Automatically loads valid entries from file on instantiation
- Auto-Save: Automatically saves to file when using
set() - Auto-Expire: Expired items are automatically filtered out
- 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
- Secret Key Required: Only parties with the secret can generate valid signatures
- One-Way Function: Cannot be reversed to get the original data
- Collision Resistant: Extremely difficult to find two inputs with the same hash
- Industry Standard: Used by HubSpot, AWS, GitHub, and other major platforms
License
CC-BY-NC-ND-4.0
Author
findmore.eu [email protected]
