Secrets Examples
This comprehensive collection of examples demonstrates how to effectively use secrets in real-world webhook automation scenarios. Each example includes complete code, security considerations, and best practices.
URL Template Examples
1. Telegram Bot Integration (No Code Required)
The simplest way to use secrets is via URL templates in your destination configuration:
Connection Setup:
- Source:
https://your-hooklistener-url.com
- Destination URL:
https://api.telegram.org/bot{{TELEGRAM_BOT_TOKEN}}/sendMessage
Required Secret:
curl -X POST https://api.hooklistener.com/api/v1/secrets \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-d '{
"secret": {
"name": "TELEGRAM_BOT_TOKEN",
"value": "123456789:ABCdefGHIjklmnoPQRsTuVwxyZ",
"description": "Telegram bot token for webhook notifications"
}
}'
How it works:
- Incoming webhook arrives at your source URL
- Hooklistener automatically substitutes
{{TELEGRAM_BOT_TOKEN}}
with the secret value - Webhook forwards to:
https://api.telegram.org/bot123456789:ABCdefGHIjklmnoPQRsTuVwxyZ/sendMessage
- No transformation code needed!
2. Slack Incoming Webhook
Send webhooks directly to Slack using webhook URLs:
Destination URL: https://hooks.slack.com/services/{{SLACK_WEBHOOK_PATH}}
Required Secret:
curl -X POST https://api.hooklistener.com/api/v1/secrets \
-d '{
"secret": {
"name": "SLACK_WEBHOOK_PATH",
"value": "T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX",
"description": "Slack webhook path for #alerts channel"
}
}'
3. Multi-Parameter API Endpoints
Use multiple secrets in a single destination URL:
Destination URL: https://api.example.com/webhook?token={{API_TOKEN}}&secret={{WEBHOOK_SECRET}}&key={{SERVICE_KEY}}
Required Secrets:
# API Token
curl -X POST https://api.hooklistener.com/api/v1/secrets \
-d '{"secret": {"name": "API_TOKEN", "value": "tok_abc123", "description": "Main API token"}}'
# Webhook Secret
curl -X POST https://api.hooklistener.com/api/v1/secrets \
-d '{"secret": {"name": "WEBHOOK_SECRET", "value": "whsec_def456", "description": "Webhook verification secret"}}'
# Service Key
curl -X POST https://api.hooklistener.com/api/v1/secrets \
-d '{"secret": {"name": "SERVICE_KEY", "value": "sk_ghi789", "description": "Service-specific authentication key"}}'
4. RESTful API with Path Parameters
Embed secrets in URL paths:
Destination URL: https://api.service.com/v1/accounts/{{ACCOUNT_ID}}/webhooks/{{WEBHOOK_ID}}/events
Use Case: Multi-tenant applications where account and webhook IDs are sensitive
Transformation-Based Examples
5. Slack Bot Integration with Custom Logic
Securely authenticate with Slack's API using bot tokens:
addHandler('transform', async (request, context) => {
const slackToken = process.env.SLACK_BOT_TOKEN;
if (!slackToken) {
console.log('Warning: SLACK_BOT_TOKEN not configured');
return request;
}
// Convert webhook to Slack message format
const githubEvent = request.body;
if (githubEvent.action === 'opened' && githubEvent.pull_request) {
const pr = githubEvent.pull_request;
return {
method: 'POST',
url: 'https://hooks.slack.com/services/YOUR/WEBHOOK/PATH',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${slackToken}`
},
body: {
text: `New PR: ${pr.title}`,
blocks: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: `*${pr.title}*\nby ${pr.user.login}`
},
accessory: {
type: 'button',
text: { type: 'plain_text', text: 'View PR' },
url: pr.html_url
}
}
]
}
};
}
return null; // Skip other events
});
Required Secret:
curl -X POST https://api.hooklistener.com/api/v1/secrets \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-d '{
"secret": {
"name": "SLACK_BOT_TOKEN",
"value": "xoxb-your-slack-bot-token",
"description": "Bot token for GitHub notifications"
}
}'
2. Multiple API Keys for Different Services
Manage multiple service integrations with different authentication methods:
addHandler('transform', async (request, context) => {
const event = request.body;
// Route to different services based on event type
switch (event.type) {
case 'payment.succeeded':
return routeToSlack(request);
case 'user.created':
return routeToHubSpot(request);
case 'error.critical':
return routeToPagerDuty(request);
default:
return request;
}
function routeToSlack(request) {
const token = process.env.SLACK_BOT_TOKEN;
if (!token) return request;
return {
...request,
url: 'https://hooks.slack.com/services/YOUR/WEBHOOK/PATH',
headers: {
...request.headers,
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
};
}
function routeToHubSpot(request) {
const apiKey = process.env.HUBSPOT_API_KEY;
if (!apiKey) return request;
return {
...request,
url: 'https://api.hubapi.com/crm/v3/objects/contacts',
headers: {
...request.headers,
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
}
};
}
function routeToPagerDuty(request) {
const integrationKey = process.env.PAGERDUTY_INTEGRATION_KEY;
if (!integrationKey) return request;
return {
method: 'POST',
url: 'https://events.pagerduty.com/v2/enqueue',
headers: {
'Content-Type': 'application/json'
},
body: {
routing_key: integrationKey,
event_action: 'trigger',
payload: {
summary: event.message || 'Critical Error',
severity: 'critical',
source: event.source || 'webhook-automation'
}
}
};
}
});
Required Secrets:
# Slack Bot Token
curl -X POST https://api.hooklistener.com/api/v1/secrets \
-d '{"secret": {"name": "SLACK_BOT_TOKEN", "value": "xoxb-...", "description": "Slack notifications"}}'
# HubSpot API Key
curl -X POST https://api.hooklistener.com/api/v1/secrets \
-d '{"secret": {"name": "HUBSPOT_API_KEY", "value": "pat-na1-...", "description": "HubSpot CRM integration"}}'
# PagerDuty Integration Key
curl -X POST https://api.hooklistener.com/api/v1/secrets \
-d '{"secret": {"name": "PAGERDUTY_INTEGRATION_KEY", "value": "R032....", "description": "Critical alerts"}}'
Webhook Signature Verification
3. GitHub Webhook Security
Verify GitHub webhook signatures for security:
addHandler('transform', async (request, context) => {
const webhookSecret = process.env.GITHUB_WEBHOOK_SECRET;
if (!webhookSecret) {
console.log('Warning: GITHUB_WEBHOOK_SECRET not configured');
// In production, you might want to reject unsigned webhooks
return request;
}
// Verify GitHub signature
const signature = request.headers['x-hub-signature-256'];
const payload = JSON.stringify(request.body);
if (!verifyGitHubSignature(payload, signature, webhookSecret)) {
console.log('Invalid GitHub signature - rejecting webhook');
return null; // Reject invalid webhook
}
console.log('✅ GitHub signature verified');
// Process verified webhook
const event = request.body;
if (event.action === 'opened' && event.pull_request) {
return {
...request,
body: {
...event,
verified: true,
processed_at: new Date().toISOString()
}
};
}
return request;
function verifyGitHubSignature(payload, signature, secret) {
// This is a simplified example - GitHub uses HMAC SHA256
// In practice, you'd use a proper HMAC verification
const expectedSignature = `sha256=${createHash(payload + secret)}`;
return signature === expectedSignature;
}
function createHash(data) {
// Simplified hash function for example
// In real implementation, use proper HMAC SHA256
return 'example_hash_' + data.length;
}
});
Required Secret:
curl -X POST https://api.hooklistener.com/api/v1/secrets \
-d '{
"secret": {
"name": "GITHUB_WEBHOOK_SECRET",
"value": "your_github_webhook_secret_here",
"description": "Secret for verifying GitHub webhook signatures"
}
}'
4. Stripe Webhook Signature Verification
Secure Stripe webhook processing with signature verification:
addHandler('transform', async (request, context) => {
const endpointSecret = process.env.STRIPE_ENDPOINT_SECRET;
if (!endpointSecret) {
console.log('Warning: STRIPE_ENDPOINT_SECRET not configured');
return request;
}
// Get Stripe signature from headers
const signature = request.headers['stripe-signature'];
const timestamp = extractTimestamp(signature);
const receivedSignature = extractSignature(signature);
// Verify signature
const payload = JSON.stringify(request.body);
const expectedSignature = computeSignature(timestamp, payload, endpointSecret);
if (receivedSignature !== expectedSignature) {
console.log('Invalid Stripe signature - rejecting webhook');
return null;
}
// Check timestamp to prevent replay attacks (5 minutes tolerance)
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - timestamp) > 300) {
console.log('Stripe webhook timestamp too old - rejecting');
return null;
}
console.log('✅ Stripe signature verified');
// Process verified Stripe event
const event = request.body;
if (event.type === 'payment_intent.succeeded') {
const paymentIntent = event.data.object;
return {
method: 'POST',
url: request.url,
headers: {
...request.headers,
'Content-Type': 'application/json'
},
body: {
event_type: 'payment_success',
amount: paymentIntent.amount,
currency: paymentIntent.currency,
customer_id: paymentIntent.customer,
verified: true,
stripe_event_id: event.id,
processed_at: new Date().toISOString()
}
};
}
return request;
function extractTimestamp(signature) {
// Extract timestamp from Stripe signature
const parts = signature.split(',');
for (const part of parts) {
if (part.startsWith('t=')) {
return parseInt(part.substring(2));
}
}
return 0;
}
function extractSignature(signature) {
// Extract signature from Stripe signature
const parts = signature.split(',');
for (const part of parts) {
if (part.startsWith('v1=')) {
return part.substring(3);
}
}
return '';
}
function computeSignature(timestamp, payload, secret) {
// Simplified signature computation
// In real implementation, use HMAC SHA256
return `computed_${timestamp}_${payload.length}_${secret.length}`;
}
});
Database and Service Integration
5. Database Connection with Credentials
Securely connect to databases using connection strings:
addHandler('transform', async (request, context) => {
const databaseUrl = process.env.DATABASE_URL;
const redisUrl = process.env.REDIS_URL;
if (!databaseUrl || !redisUrl) {
console.log('Missing database configuration');
return request;
}
// Parse connection details (don't log the full URLs with credentials)
console.log('Database configured:', !!databaseUrl);
console.log('Redis configured:', !!redisUrl);
const event = request.body;
// Transform webhook into database operation
if (event.type === 'user.created') {
return {
...request,
url: 'https://your-api.com/database/users',
headers: {
...request.headers,
'Content-Type': 'application/json',
'X-Database-Connection': 'configured', // Don't expose actual URL
'X-Cache-Connection': 'configured'
},
body: {
operation: 'INSERT',
table: 'users',
data: {
user_id: event.data.id,
email: event.data.email,
created_at: event.data.created_at
},
// Database credentials handled securely by receiving service
connection_hint: 'use_database_url_secret'
}
};
}
return request;
});
Required Secrets:
# Database connection string
curl -X POST https://api.hooklistener.com/api/v1/secrets \
-d '{
"secret": {
"name": "DATABASE_URL",
"value": "postgresql://user:password@host:5432/dbname",
"description": "Main application database connection"
}
}'
# Redis connection string
curl -X POST https://api.hooklistener.com/api/v1/secrets \
-d '{
"secret": {
"name": "REDIS_URL",
"value": "redis://user:password@host:6379/0",
"description": "Redis cache for session storage"
}
}'
6. Multi-Environment Configuration
Handle different secrets for development, staging, and production:
addHandler('transform', async (request, context) => {
// Environment-specific secrets
const environment = process.env.ENVIRONMENT || 'production';
const apiKey = process.env[`API_KEY_${environment.toUpperCase()}`];
const databaseUrl = process.env[`DATABASE_URL_${environment.toUpperCase()}`];
console.log(`Running in ${environment} environment`);
console.log('API Key configured:', !!apiKey);
console.log('Database configured:', !!databaseUrl);
if (!apiKey) {
console.log(`Warning: API_KEY_${environment.toUpperCase()} not configured`);
return request;
}
// Use environment-specific configuration
const baseUrl = getBaseUrlForEnvironment(environment);
return {
...request,
url: `${baseUrl}/api/webhooks`,
headers: {
...request.headers,
'Authorization': `Bearer ${apiKey}`,
'X-Environment': environment
},
body: {
...request.body,
environment: environment,
processed_at: new Date().toISOString()
}
};
function getBaseUrlForEnvironment(env) {
switch (env) {
case 'development':
return 'https://dev.api.example.com';
case 'staging':
return 'https://staging.api.example.com';
case 'production':
return 'https://api.example.com';
default:
return 'https://api.example.com';
}
}
});
Environment-Specific Secrets:
# Development
curl -X POST https://api.hooklistener.com/api/v1/secrets \
-d '{"secret": {"name": "API_KEY_DEVELOPMENT", "value": "dev_key_123", "description": "Dev environment API key"}}'
# Staging
curl -X POST https://api.hooklistener.com/api/v1/secrets \
-d '{"secret": {"name": "API_KEY_STAGING", "value": "staging_key_456", "description": "Staging environment API key"}}'
# Production
curl -X POST https://api.hooklistener.com/api/v1/secrets \
-d '{"secret": {"name": "API_KEY_PRODUCTION", "value": "prod_key_789", "description": "Production environment API key"}}'
Advanced Security Patterns
7. Dynamic Secret Selection
Choose different secrets based on webhook content:
addHandler('transform', async (request, context) => {
const event = request.body;
// Select appropriate credentials based on tenant/client
let apiKeyName, webhookSecretName;
if (event.tenant_id) {
// Multi-tenant setup with per-tenant secrets
apiKeyName = `API_KEY_TENANT_${event.tenant_id}`;
webhookSecretName = `WEBHOOK_SECRET_TENANT_${event.tenant_id}`;
} else if (event.environment) {
// Environment-based secret selection
apiKeyName = `API_KEY_${event.environment.toUpperCase()}`;
webhookSecretName = `WEBHOOK_SECRET_${event.environment.toUpperCase()}`;
} else {
// Default secrets
apiKeyName = 'API_KEY_DEFAULT';
webhookSecretName = 'WEBHOOK_SECRET_DEFAULT';
}
const apiKey = process.env[apiKeyName];
const webhookSecret = process.env[webhookSecretName];
console.log(`Using secrets: ${apiKeyName}, ${webhookSecretName}`);
console.log('API Key found:', !!apiKey);
console.log('Webhook Secret found:', !!webhookSecret);
if (!apiKey || !webhookSecret) {
console.log('Required secrets not found for this configuration');
return {
...request,
body: {
error: 'Configuration not found',
tenant_id: event.tenant_id,
environment: event.environment
}
};
}
// Use selected secrets
return {
...request,
headers: {
...request.headers,
'Authorization': `Bearer ${apiKey}`,
'X-Webhook-Signature': generateSignature(request.body, webhookSecret)
},
body: {
...request.body,
tenant_id: event.tenant_id,
authenticated: true
}
};
function generateSignature(payload, secret) {
// Generate webhook signature with secret
return `sha256=${JSON.stringify(payload).length}_${secret.length}`;
}
});
8. Secret Validation and Health Checks
Validate secrets and provide health check information:
addHandler('transform', async (request, context) => {
// Health check endpoint
if (request.url.includes('/health')) {
return performHealthCheck();
}
// Regular webhook processing
return processWebhook(request);
function performHealthCheck() {
const requiredSecrets = [
'SLACK_BOT_TOKEN',
'DATABASE_URL',
'REDIS_URL',
'API_KEY_PRODUCTION',
'WEBHOOK_SECRET'
];
const secretStatus = {};
let allSecretsPresent = true;
for (const secretName of requiredSecrets) {
const isPresent = !!process.env[secretName];
secretStatus[secretName] = isPresent ? 'configured' : 'missing';
if (!isPresent) {
allSecretsPresent = false;
console.log(`❌ Missing required secret: ${secretName}`);
}
}
console.log(`Health check: ${allSecretsPresent ? '✅ All secrets configured' : '❌ Missing secrets'}`);
return {
method: 'GET',
url: '/health',
headers: { 'Content-Type': 'application/json' },
body: {
status: allSecretsPresent ? 'healthy' : 'unhealthy',
secrets: secretStatus,
timestamp: new Date().toISOString(),
environment: process.env.ENVIRONMENT || 'unknown'
}
};
}
function processWebhook(request) {
// Validate all required secrets are present
const apiKey = process.env.API_KEY_PRODUCTION;
const webhookSecret = process.env.WEBHOOK_SECRET;
if (!apiKey || !webhookSecret) {
console.log('Cannot process webhook - missing required secrets');
return {
...request,
body: {
error: 'Service configuration error',
message: 'Required secrets not configured'
}
};
}
// Process with validated secrets
return {
...request,
headers: {
...request.headers,
'Authorization': `Bearer ${apiKey}`
},
body: {
...request.body,
processed: true,
validated: true
}
};
}
});
Error Handling and Fallbacks
9. Graceful Degradation
Handle missing secrets gracefully with fallback behavior:
addHandler('transform', async (request, context) => {
const event = request.body;
// Try primary notification channel (Slack)
const slackToken = process.env.SLACK_BOT_TOKEN;
if (slackToken) {
console.log('Using primary notification channel: Slack');
return routeToSlack(request, slackToken);
}
// Fallback to secondary channel (Discord)
const discordWebhook = process.env.DISCORD_WEBHOOK_URL;
if (discordWebhook) {
console.log('Primary channel unavailable, using fallback: Discord');
return routeToDiscord(request, discordWebhook);
}
// Final fallback to email
const emailApiKey = process.env.EMAIL_API_KEY;
if (emailApiKey) {
console.log('Secondary channel unavailable, using fallback: Email');
return routeToEmail(request, emailApiKey);
}
// No notification channels available
console.log('❌ No notification channels configured');
return {
...request,
body: {
...event,
error: 'No notification channels available',
attempted_channels: ['slack', 'discord', 'email'],
original_event: event
}
};
function routeToSlack(request, token) {
return {
...request,
url: 'https://hooks.slack.com/services/YOUR/WEBHOOK/PATH',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: formatForSlack(request.body)
};
}
function routeToDiscord(request, webhookUrl) {
return {
...request,
url: webhookUrl,
headers: { 'Content-Type': 'application/json' },
body: formatForDiscord(request.body)
};
}
function routeToEmail(request, apiKey) {
return {
...request,
url: 'https://api.sendgrid.com/v3/mail/send',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: formatForEmail(request.body)
};
}
function formatForSlack(event) {
return {
text: `Alert: ${event.message}`,
blocks: [{
type: 'section',
text: { type: 'mrkdwn', text: `*${event.type}*: ${event.message}` }
}]
};
}
function formatForDiscord(event) {
return {
content: `🚨 ${event.type}: ${event.message}`,
embeds: [{
title: event.type,
description: event.message,
color: 0xff0000,
timestamp: new Date().toISOString()
}]
};
}
function formatForEmail(event) {
return {
personalizations: [{
to: [{ email: '[email protected]' }]
}],
from: { email: '[email protected]' },
subject: `Alert: ${event.type}`,
content: [{
type: 'text/plain',
value: `Alert: ${event.message}\n\nDetails: ${JSON.stringify(event, null, 2)}`
}]
};
}
});
Best Practices Summary
Secret Organization
- Use descriptive names:
STRIPE_API_KEY
notAPI_KEY
- Group by service:
SLACK_BOT_TOKEN
,SLACK_WEBHOOK_URL
- Include environment:
API_KEY_PRODUCTION
,API_KEY_STAGING
- Add descriptions: Document what each secret is used for
Security Practices
- Validate presence: Always check if secrets exist before using
- Never log values: Log only presence/absence of secrets
- Use minimal permissions: API keys should have least privilege
- Handle missing secrets gracefully: Provide fallback behavior
- Monitor usage: Review
last_used_at
regularly
Error Handling
- Fail gracefully: Don't break entire transformation for missing secrets
- Provide alternatives: Have fallback authentication methods
- Log appropriately: Debug information without exposing secrets
- Return meaningful errors: Help diagnose configuration issues
Testing Your Secret Integration
Use the test endpoint to verify secret access:
curl -X POST https://api.hooklistener.com/api/v1/transformations/test \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"transformation": {
"code": "addHandler('\''transform'\'', (req) => ({ ...req, body: { secrets_available: Object.keys(process.env) } }));"
},
"request": {
"method": "POST",
"url": "https://example.com/webhook",
"body": { "test": true }
}
}'
This will show you which secrets are available to your transformations without exposing their values.
Secrets are fundamental to secure webhook automation. Start with simple authentication patterns and gradually build more sophisticated multi-service integrations as your needs grow.