Fetch Webhook
Overview
Retrieve the details of a specific Webhook by its ID. View webhook URL, enabled events, authentication configuration, and enabled status.
Resource Access
- User Permissions: Admin users only
- Endpoint:
GET /webhooks/\{webhook_id}
Arguments
| Parameter | Type | Required | Description |
|---|---|---|---|
| webhook_id | string | Yes | The ID of the Webhook to retrieve |
Example Request
curl -X GET \
'https://api.ahrvo.network/payments/na/webhooks/WHwebhook123' \
-u username:password
Example Response
{
"id": "WHwebhook123",
"created_at": "2023-12-10T20:00:00Z",
"updated_at": "2023-12-11T15:30:00Z",
"url": "https://api.yourcompany.com/webhooks/finix",
"enabled": true,
"authentication": {
"type": "BEARER",
"bearer": {
"token": "your-secret-bearer-token"
}
},
"enabled_events": [
{
"entity": "transfer",
"types": ["succeeded", "failed"]
},
{
"entity": "merchant",
"types": ["verification.succeeded", "verification.failed"]
}
],
"secret_signing_key": null,
"_links": {
"self": {
"href": "https://api.ahrvo.network/payments/na/webhooks/WHwebhook123"
}
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
| id | string | Unique webhook identifier (starts with "WH") |
| created_at | string | ISO 8601 timestamp when webhook was created |
| updated_at | string | ISO 8601 timestamp of last modification |
| url | string | HTTPS endpoint where events are sent |
| enabled | boolean | Whether webhook is active (true) or disabled (false) |
| authentication | object | Authentication configuration for webhook endpoint |
| authentication.type | string | "NONE", "BASIC", or "BEARER" |
| authentication.basic | object | Present if type is "BASIC" |
| authentication.basic.username | string | HTTP Basic Auth username |
| authentication.basic.password | string | HTTP Basic Auth password |
| authentication.bearer | object | Present if type is "BEARER" |
| authentication.bearer.token | string | Bearer token for Authorization header |
| enabled_events | array | Event filters (empty = all events) |
| enabled_events[].entity | string | Entity type (transfer, merchant, etc.) |
| enabled_events[].types | array | Event types for this entity |
| secret_signing_key | null | Always null (only returned on creation) |
| _links | object | HATEOAS links |
Additional Information
-
Secret Signing Key: Never returned
- Always null in GET responses
- Only returned on initial creation
- Cannot retrieve after creation
- Security measure
- Must store when webhook created
- Create new webhook if lost
-
Enabled Status: Controls event delivery
- true: Webhook actively sends events
- false: Webhook disabled, no events sent
- Use PUT to toggle
- Temporarily pause without deleting
-
Authentication: Endpoint security
- NONE: No authentication sent to your endpoint
- BASIC: HTTP Basic Auth headers sent
- BEARER: Bearer token in Authorization header
- Credentials visible in response (protect API access)
- Use to secure your webhook endpoint
-
Enabled Events: Event filtering
- Empty array
[]: All events sent - Specific entities/types: Only those events sent
- Reduces noise
- Can update via PUT
- Empty array
-
Created vs Updated: Timestamps
created_at: Never changesupdated_at: Changes on PUT requests- Both in ISO 8601 format
- UTC timezone
-
URL: Webhook endpoint
- Must be HTTPS
- Your server URL
- Where events are POSTed
- Can update via PUT
-
Webhook ID: Permanent identifier
- Starts with "WH"
- Never changes
- Use to fetch/update
Use Cases
Verify Webhook Configuration
// Check specific webhook settings
async function verifyWebhookConfig(webhookId) {
const webhook = await fetchWebhook(webhookId);
console.log('Webhook Configuration:');
console.log(`URL: ${webhook.url}`);
console.log(`Enabled: ${webhook.enabled}`);
console.log(`Auth Type: ${webhook.authentication.type}`);
if (webhook.enabled_events.length === 0) {
console.log('Events: ALL events');
} else {
console.log('Events:');
webhook.enabled_events.forEach(event => {
console.log(` ${event.entity}: ${event.types.join(', ')}`);
});
}
return webhook;
}
Check if Webhook is Active
// Verify webhook is enabled before relying on it
async function isWebhookActive(webhookId) {
const webhook = await fetchWebhook(webhookId);
if (!webhook.enabled) {
console.warn(`Webhook ${webhookId} is DISABLED!`);
return false;
}
console.log(`✓ Webhook ${webhookId} is active`);
return true;
}
Audit Webhook Security
// Check webhook has proper authentication
async function auditWebhookSecurity(webhookId) {
const webhook = await fetchWebhook(webhookId);
const issues = [];
// Check HTTPS
if (!webhook.url.startsWith('https://')) {
issues.push('URL is not HTTPS');
}
// Check authentication
if (webhook.authentication.type === 'NONE') {
issues.push('No authentication configured');
}
// Check for test URLs in production
const testDomains = ['webhook.site', 'requestbin', 'ngrok', 'localhost'];
if (testDomains.some(domain => webhook.url.includes(domain))) {
issues.push('Using test/development URL');
}
if (issues.length > 0) {
console.error(`Security issues for webhook ${webhookId}:`);
issues.forEach(issue => console.error(` - ${issue}`));
return false;
}
console.log(`✓ Webhook ${webhookId} passes security audit`);
return true;
}
Compare Webhook Settings
// Compare two webhooks (e.g., dev vs prod)
async function compareWebhooks(webhookId1, webhookId2) {
const webhook1 = await fetchWebhook(webhookId1);
const webhook2 = await fetchWebhook(webhookId2);
console.log('Comparison:');
console.log(`URL: ${webhook1.url} vs ${webhook2.url}`);
console.log(`Enabled: ${webhook1.enabled} vs ${webhook2.enabled}`);
console.log(`Auth: ${webhook1.authentication.type} vs ${webhook2.authentication.type}`);
// Compare events
const events1 = webhook1.enabled_events.map(e => `${e.entity}:${e.types.join(',')}`).sort();
const events2 = webhook2.enabled_events.map(e => `${e.entity}:${e.types.join(',')}`).sort();
if (JSON.stringify(events1) === JSON.stringify(events2)) {
console.log('Events: Identical');
} else {
console.log('Events: Different');
console.log(' Webhook 1:', events1);
console.log(' Webhook 2:', events2);
}
}
Get Webhook URL
// Extract webhook URL for documentation
async function getWebhookUrl(webhookId) {
const webhook = await fetchWebhook(webhookId);
return webhook.url;
}
Check Event Subscription
// See if webhook receives specific event
async function isEventEnabled(webhookId, entity, type) {
const webhook = await fetchWebhook(webhookId);
// All events enabled
if (webhook.enabled_events.length === 0) {
console.log(`Webhook receives ALL events (including ${entity}.${type})`);
return true;
}
// Check specific event
const entityConfig = webhook.enabled_events.find(e => e.entity === entity);
if (!entityConfig) {
console.log(`Webhook does not receive ${entity} events`);
return false;
}
if (entityConfig.types.includes(type)) {
console.log(`✓ Webhook receives ${entity}.${type}`);
return true;
} else {
console.log(`Webhook receives ${entity} events, but not ${type}`);
console.log(` Enabled types: ${entityConfig.types.join(', ')}`);
return false;
}
}
// Example usage
await isEventEnabled('WHwebhook123', 'transfer', 'succeeded');
Monitor Webhook Changes
// Check if webhook was recently updated
async function checkRecentChanges(webhookId, hours = 24) {
const webhook = await fetchWebhook(webhookId);
const updated = new Date(webhook.updated_at);
const created = new Date(webhook.created_at);
const now = new Date();
const hoursSinceUpdate = (now - updated) / (1000 * 60 * 60);
const hoursSinceCreation = (now - created) / (1000 * 60 * 60);
if (hoursSinceUpdate < hours) {
console.log(`⚠ Webhook modified ${hoursSinceUpdate.toFixed(1)} hours ago`);
console.log(`Last updated: ${webhook.updated_at}`);
if (hoursSinceCreation < hours) {
console.log('(Recently created)');
} else {
console.log('(Existing webhook was modified)');
}
return true;
}
console.log(`No recent changes (last updated ${hoursSinceUpdate.toFixed(1)} hours ago)`);
return false;
}
Extract Authentication Details
// Get authentication for webhook endpoint
async function getAuthDetails(webhookId) {
const webhook = await fetchWebhook(webhookId);
console.log(`Webhook ${webhookId} authentication:`);
switch (webhook.authentication.type) {
case 'NONE':
console.log('No authentication');
return null;
case 'BASIC':
console.log('HTTP Basic Authentication');
console.log(`Username: ${webhook.authentication.basic.username}`);
console.log(`Password: ${webhook.authentication.basic.password}`);
return {
type: 'basic',
username: webhook.authentication.basic.username,
password: webhook.authentication.basic.password
};
case 'BEARER':
console.log('Bearer Token Authentication');
console.log(`Token: ${webhook.authentication.bearer.token}`);
return {
type: 'bearer',
token: webhook.authentication.bearer.token
};
}
}
Best Practices
-
Check Before Update: Verify current state
- Fetch webhook before updating
- See what will change
- Avoid unintended modifications
- Compare old vs new settings
-
Regular Audits: Verify configuration
- Periodic checks (monthly)
- Verify URL still correct
- Check authentication secure
- Confirm events appropriate
- Validate enabled status
-
Documentation: Record webhook details
async function documentWebhook(webhookId) {
const webhook = await fetchWebhook(webhookId);
const doc = {
id: webhook.id,
purpose: 'Production payment notifications', // Add manually
url: webhook.url,
environment: webhook.url.includes('production') ? 'Production' : 'Other',
enabled: webhook.enabled,
auth_type: webhook.authentication.type,
events: webhook.enabled_events.length === 0
? 'All events'
: webhook.enabled_events.map(e => `${e.entity}:${e.types.join(',')}`).join('; '),
created: webhook.created_at,
last_updated: webhook.updated_at
};
return doc;
} -
Security: Protect credentials
- API response shows auth credentials
- Don't log/expose webhook details
- Limit who can fetch webhooks
- Rotate credentials if exposed
-
Monitoring: Track webhook status
- Alert if webhook disabled
- Monitor for configuration changes
- Track update timestamps
- Verify URLs periodically
Common Workflows
Before Disabling Webhook
// Check webhook usage before disabling
async function beforeDisable(webhookId) {
const webhook = await fetchWebhook(webhookId);
console.log('Webhook to disable:');
console.log(`URL: ${webhook.url}`);
console.log(`Created: ${webhook.created_at}`);
console.log(`Last updated: ${webhook.updated_at}`);
if (webhook.enabled_events.length === 0) {
console.log('⚠ Receives ALL events!');
} else {
console.log('Event subscriptions:');
webhook.enabled_events.forEach(e => {
console.log(` ${e.entity}: ${e.types.join(', ')}`);
});
}
// Confirm before proceeding
const confirm = await askUser('Disable this webhook? (yes/no)');
return confirm === 'yes';
}
Verify After Update
// Confirm update was applied
async function verifyUpdate(webhookId, expectedChanges) {
const webhook = await fetchWebhook(webhookId);
const matches = {
url: !expectedChanges.url || webhook.url === expectedChanges.url,
enabled: expectedChanges.enabled === undefined || webhook.enabled === expectedChanges.enabled,
auth_type: !expectedChanges.auth_type || webhook.authentication.type === expectedChanges.auth_type
};
const allMatch = Object.values(matches).every(m => m);
if (allMatch) {
console.log('✓ Update verified successfully');
} else {
console.error('✗ Update verification failed:');
Object.entries(matches).forEach(([field, match]) => {
if (!match) {
console.error(` ${field}: Did not match expected value`);
}
});
}
return allMatch;
}
Clone Webhook Configuration
// Get config to create similar webhook
async function cloneWebhookConfig(sourceWebhookId) {
const source = await fetchWebhook(sourceWebhookId);
// Extract config for new webhook creation
const config = {
url: source.url, // Update this for new webhook
enabled: source.enabled,
authentication: source.authentication,
enabled_events: source.enabled_events
};
console.log('Clone this configuration:');
console.log(JSON.stringify(config, null, 2));
console.log('\nUpdate URL before creating new webhook');
return config;
}
Troubleshoot Missing Events
// Check why webhook not receiving events
async function troubleshootMissingEvents(webhookId, missingEntity, missingType) {
const webhook = await fetchWebhook(webhookId);
console.log(`Troubleshooting: Not receiving ${missingEntity}.${missingType}`);
// Check 1: Enabled
if (!webhook.enabled) {
console.error('✗ Webhook is DISABLED');
return 'disabled';
}
console.log('✓ Webhook is enabled');
// Check 2: Event filter
if (webhook.enabled_events.length > 0) {
const entityConfig = webhook.enabled_events.find(e => e.entity === missingEntity);
if (!entityConfig) {
console.error(`✗ Not subscribed to ${missingEntity} events`);
console.log('Subscribed entities:',
webhook.enabled_events.map(e => e.entity).join(', ')
);
return 'not_subscribed_entity';
}
if (!entityConfig.types.includes(missingType)) {
console.error(`✗ Not subscribed to ${missingType} type`);
console.log(`Subscribed ${missingEntity} types:`,
entityConfig.types.join(', ')
);
return 'not_subscribed_type';
}
console.log(`✓ Subscribed to ${missingEntity}.${missingType}`);
} else {
console.log('✓ Subscribed to ALL events');
}
console.log('Webhook configuration looks correct.');
console.log('Check:');
console.log('- Is your endpoint responding with 200?');
console.log('- Check your server logs for webhook requests');
console.log('- Verify URL is accessible from internet');
return 'config_ok';
}
Security Considerations
-
Credentials Visible: Authentication shown
- Basic auth username/password exposed
- Bearer tokens exposed
- Anyone with API access can see
- Use strong webhook endpoint authentication
- Rotate if credentials compromised
-
Signing Key Not Shown: Security feature
- Always null in GET response
- Cannot retrieve after creation
- Must store when webhook created
- Create new webhook if lost
-
API Access Control: Limit access
- Only admin users can fetch webhooks
- Audit who accesses webhook details
- Monitor for unauthorized access
- Use separate credentials per environment
-
URL Exposure: Internal endpoints
- Webhook URLs visible in response
- Don't expose internal network URLs
- Use API gateway if possible
- Firewall webhook endpoints
Error Responses
Webhook Not Found
{
"status": 404,
"message": "Webhook not found",
"details": "No webhook exists with ID WHinvalid123"
}
- Invalid webhook ID
- Webhook was deleted
- Wrong environment/account
- Check ID is correct
Unauthorized
{
"status": 401,
"message": "Unauthorized"
}
- Invalid API credentials
- Not admin user
- Wrong environment
Forbidden
{
"status": 403,
"message": "Forbidden"
}
- Not admin user
- Insufficient permissions
Troubleshooting
Webhook Not Found
- Verify webhook ID
- Check correct environment
- May have been deleted
- Check different Application
Can't See Signing Key
- By design - always null
- Only shown on creation
- Security measure
- Create new webhook if needed
Authentication Doesn't Match
- Fetch shows current config
- May have been updated
- Check updated_at timestamp
- Update your records
Wrong URL Displayed
- Webhook may have been updated
- Check updated_at timestamp
- Verify correct webhook ID
- May be different environment
Related Endpoints
- POST /webhooks: Create new webhook
- GET /webhooks: List all webhooks
- PUT /webhooks/{id}: Update webhook configuration