Skip to main content

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

ParameterTypeRequiredDescription
webhook_idstringYesThe 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

FieldTypeDescription
idstringUnique webhook identifier (starts with "WH")
created_atstringISO 8601 timestamp when webhook was created
updated_atstringISO 8601 timestamp of last modification
urlstringHTTPS endpoint where events are sent
enabledbooleanWhether webhook is active (true) or disabled (false)
authenticationobjectAuthentication configuration for webhook endpoint
authentication.typestring"NONE", "BASIC", or "BEARER"
authentication.basicobjectPresent if type is "BASIC"
authentication.basic.usernamestringHTTP Basic Auth username
authentication.basic.passwordstringHTTP Basic Auth password
authentication.bearerobjectPresent if type is "BEARER"
authentication.bearer.tokenstringBearer token for Authorization header
enabled_eventsarrayEvent filters (empty = all events)
enabled_events[].entitystringEntity type (transfer, merchant, etc.)
enabled_events[].typesarrayEvent types for this entity
secret_signing_keynullAlways null (only returned on creation)
_linksobjectHATEOAS 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
  • Created vs Updated: Timestamps

    • created_at: Never changes
    • updated_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
  • POST /webhooks: Create new webhook
  • GET /webhooks: List all webhooks
  • PUT /webhooks/{id}: Update webhook configuration