Skip to main content

Update Webhook

Overview

Update a Webhook's URL, enabled status, event subscriptions, or authentication. Modify webhook configuration without recreating it.

Resource Access

  • User Permissions: Admin users only
  • Endpoint: PUT /webhooks/\{webhook_id}

Arguments

ParameterTypeRequiredDescription
webhook_idstringYesThe ID of the Webhook to update
urlstringNoNew HTTPS endpoint URL
enabledbooleanNoEnable (true) or disable (false) webhook
authenticationobjectNoAuthentication configuration
authentication.typestringNo"NONE", "BASIC", or "BEARER"
authentication.basicobjectNoRequired if type is "BASIC"
authentication.basic.usernamestringNoHTTP Basic Auth username
authentication.basic.passwordstringNoHTTP Basic Auth password
authentication.bearerobjectNoRequired if type is "BEARER"
authentication.bearer.tokenstringNoBearer token
enabled_eventsarrayNoEvent filters (empty array = all events)
enabled_events[].entitystringNoEntity type (transfer, merchant, etc.)
enabled_events[].typesarrayNoEvent types for this entity

Example Requests

Disable Webhook

curl -X PUT \
'https://api.ahrvo.network/payments/na/webhooks/WHwebhook123' \
-u username:password \
-H 'Content-Type: application/json' \
-d '{
"enabled": false
}'

Update URL

curl -X PUT \
'https://api.ahrvo.network/payments/na/webhooks/WHwebhook123' \
-u username:password \
-H 'Content-Type: application/json' \
-d '{
"url": "https://api.yourcompany.com/webhooks/v2/finix"
}'

Change Event Subscriptions

curl -X PUT \
'https://api.ahrvo.network/payments/na/webhooks/WHwebhook123' \
-u username:password \
-H 'Content-Type: application/json' \
-d '{
"enabled_events": [
{
"entity": "transfer",
"types": ["succeeded", "failed"]
},
{
"entity": "settlement",
"types": ["funding_transfer.succeeded", "funding_transfer.failed"]
}
]
}'

Subscribe to All Events

curl -X PUT \
'https://api.ahrvo.network/payments/na/webhooks/WHwebhook123' \
-u username:password \
-H 'Content-Type: application/json' \
-d '{
"enabled_events": []
}'

Update Authentication

# Change to Bearer token
curl -X PUT \
'https://api.ahrvo.network/payments/na/webhooks/WHwebhook123' \
-u username:password \
-H 'Content-Type: application/json' \
-d '{
"authentication": {
"type": "BEARER",
"bearer": {
"token": "your-new-bearer-token"
}
}
}'

Multiple Changes at Once

curl -X PUT \
'https://api.ahrvo.network/payments/na/webhooks/WHwebhook123' \
-u username:password \
-H 'Content-Type: application/json' \
-d '{
"url": "https://api.yourcompany.com/webhooks/production",
"enabled": true,
"authentication": {
"type": "BEARER",
"bearer": {
"token": "prod-bearer-token-xyz"
}
},
"enabled_events": [
{
"entity": "transfer",
"types": ["succeeded", "failed"]
}
]
}'

Example Response

{
"id": "WHwebhook123",
"created_at": "2023-12-10T20:00:00Z",
"updated_at": "2023-12-15T14:30:00Z",
"url": "https://api.yourcompany.com/webhooks/v2/finix",
"enabled": true,
"authentication": {
"type": "BEARER",
"bearer": {
"token": "your-new-bearer-token"
}
},
"enabled_events": [
{
"entity": "transfer",
"types": ["succeeded", "failed"]
},
{
"entity": "settlement",
"types": ["funding_transfer.succeeded", "funding_transfer.failed"]
}
],
"secret_signing_key": null,
"_links": {
"self": {
"href": "https://api.ahrvo.network/payments/na/webhooks/WHwebhook123"
}
}
}

Additional Information

  • Partial Updates: Only send what changes

    • Send only fields to update
    • Other fields remain unchanged
    • More efficient than full replacement
    • Reduces errors
  • Signing Key: Cannot update

    • secret_signing_key is read-only
    • Set only on creation
    • Cannot change after creation
    • Create new webhook if need new key
  • URL Changes: Update endpoint

    • Must be HTTPS
    • Validates URL format
    • Events will go to new URL immediately
    • Test new endpoint first
  • Enable/Disable: Quick toggle

    • true: Start sending events
    • false: Stop sending events
    • Doesn't delete webhook
    • Preserves configuration
    • Use for temporary pause
  • Authentication: Update credentials

    • Change type (NONE → BASIC → BEARER)
    • Update username/password
    • Rotate bearer token
    • Events use new auth immediately
  • Enabled Events: Modify subscriptions

    • Empty array []: All events
    • Specific entities/types: Filtered events
    • Add new event types
    • Remove unwanted events
    • Replaces entire array (not merged)
  • Updated Timestamp: Changes on update

    • updated_at reflects last modification
    • created_at never changes
    • ISO 8601 format
    • UTC timezone

Use Cases

Temporarily Disable Webhook

// Pause webhook during maintenance
async function pauseWebhook(webhookId) {
console.log('Disabling webhook during maintenance...');

const updated = await updateWebhook(webhookId, {
enabled: false
});

console.log(`✓ Webhook disabled at ${updated.updated_at}`);
console.log('Re-enable after maintenance with enabled: true');

return updated;
}

// Later, re-enable
async function resumeWebhook(webhookId) {
const updated = await updateWebhook(webhookId, {
enabled: true
});

console.log(`✓ Webhook re-enabled at ${updated.updated_at}`);
return updated;
}

Migrate to New Endpoint

// Update webhook to point to new server
async function migrateWebhookUrl(webhookId, newUrl) {
console.log(`Migrating webhook to ${newUrl}`);

// Get current config
const current = await fetchWebhook(webhookId);
console.log(`Current URL: ${current.url}`);

// Update URL
const updated = await updateWebhook(webhookId, {
url: newUrl
});

console.log(`✓ URL updated to ${updated.url}`);
console.log('Events will now be sent to new endpoint');

return updated;
}

Add Event Subscriptions

// Subscribe to additional events
async function addEventSubscription(webhookId, entity, types) {
// Get current subscriptions
const current = await fetchWebhook(webhookId);

// Build new subscriptions (must replace entire array)
const newEvents = [...current.enabled_events];

// Check if entity already exists
const existingIndex = newEvents.findIndex(e => e.entity === entity);

if (existingIndex >= 0) {
// Merge types
const existingTypes = new Set(newEvents[existingIndex].types);
types.forEach(t => existingTypes.add(t));
newEvents[existingIndex].types = Array.from(existingTypes);
} else {
// Add new entity
newEvents.push({ entity, types });
}

const updated = await updateWebhook(webhookId, {
enabled_events: newEvents
});

console.log(`✓ Added ${entity} events:`, types);
return updated;
}

// Example: Add merchant verification events
await addEventSubscription(
'WHwebhook123',
'merchant',
['verification.succeeded', 'verification.failed']
);

Rotate Authentication Credentials

// Update webhook authentication token
async function rotateWebhookToken(webhookId, newToken) {
console.log('Rotating webhook bearer token...');

const updated = await updateWebhook(webhookId, {
authentication: {
type: 'BEARER',
bearer: {
token: newToken
}
}
});

console.log('✓ Bearer token rotated');
console.log('Update your webhook endpoint to accept new token');

return updated;
}

// Rotate Basic Auth credentials
async function rotateBasicAuth(webhookId, newUsername, newPassword) {
const updated = await updateWebhook(webhookId, {
authentication: {
type: 'BASIC',
basic: {
username: newUsername,
password: newPassword
}
}
});

console.log('✓ Basic auth credentials rotated');
return updated;
}

Remove Event Filters (Subscribe to All)

// Change from filtered events to all events
async function subscribeToAllEvents(webhookId) {
const current = await fetchWebhook(webhookId);

if (current.enabled_events.length === 0) {
console.log('Already subscribed to all events');
return current;
}

console.log('Current subscriptions:');
current.enabled_events.forEach(e => {
console.log(` ${e.entity}: ${e.types.join(', ')}`);
});

const updated = await updateWebhook(webhookId, {
enabled_events: []
});

console.log('✓ Now subscribed to ALL events');
return updated;
}

Switch Authentication Type

// Change from BASIC to BEARER
async function switchToBearerAuth(webhookId, bearerToken) {
const current = await fetchWebhook(webhookId);

console.log(`Current auth: ${current.authentication.type}`);

const updated = await updateWebhook(webhookId, {
authentication: {
type: 'BEARER',
bearer: {
token: bearerToken
}
}
});

console.log(`✓ Switched to BEARER authentication`);
return updated;
}

// Change from BEARER to BASIC
async function switchToBasicAuth(webhookId, username, password) {
const updated = await updateWebhook(webhookId, {
authentication: {
type: 'BASIC',
basic: {
username: username,
password: password
}
}
});

console.log(`✓ Switched to BASIC authentication`);
return updated;
}

// Remove authentication
async function removeAuth(webhookId) {
const updated = await updateWebhook(webhookId, {
authentication: {
type: 'NONE'
}
});

console.log(`✓ Authentication removed`);
console.warn('⚠ Webhook endpoint is now unprotected');
return updated;
}

Reduce Event Noise

// Filter to only critical events
async function filterToCriticalEvents(webhookId) {
console.log('Filtering to critical payment events only...');

const updated = await updateWebhook(webhookId, {
enabled_events: [
{
entity: 'transfer',
types: ['succeeded', 'failed']
},
{
entity: 'settlement',
types: ['funding_transfer.succeeded', 'funding_transfer.failed']
}
]
});

console.log('✓ Now receiving only critical transfer and settlement events');
return updated;
}

Update Multiple Webhooks

// Bulk update multiple webhooks
async function bulkUpdateWebhooks(webhookIds, changes) {
console.log(`Updating ${webhookIds.length} webhooks...`);

const results = await Promise.all(
webhookIds.map(id => updateWebhook(id, changes))
);

console.log(`✓ Updated ${results.length} webhooks`);
return results;
}

// Example: Disable all test webhooks
const testWebhookIds = ['WHtest1', 'WHtest2', 'WHtest3'];
await bulkUpdateWebhooks(testWebhookIds, { enabled: false });

Best Practices

  • Test Before Update: Verify changes safe

    • Test new URL accessible
    • Verify authentication works
    • Confirm event filters correct
    • Fetch current config first
  • Partial Updates: Only send changes

    // Good - only update what changes
    await updateWebhook('WHwebhook123', {
    enabled: false
    });

    // Avoid - sending unnecessary fields
    await updateWebhook('WHwebhook123', {
    url: current.url, // Unchanged
    enabled: false,
    authentication: current.authentication, // Unchanged
    enabled_events: current.enabled_events // Unchanged
    });
  • Verify After Update: Confirm changes applied

    async function updateAndVerify(webhookId, changes) {
    const before = await fetchWebhook(webhookId);
    const updated = await updateWebhook(webhookId, changes);

    // Verify each change
    Object.keys(changes).forEach(key => {
    if (JSON.stringify(updated[key]) !== JSON.stringify(changes[key])) {
    console.error(`${key} did not update correctly`);
    } else {
    console.log(`${key} updated successfully`);
    }
    });

    return updated;
    }
  • Gradual Migration: Update carefully

    • Don't change URL and events simultaneously
    • Update URL first, verify events arrive
    • Then update event filters if needed
    • Reduces troubleshooting complexity
  • Document Changes: Track modifications

    async function updateWithLog(webhookId, changes, reason) {
    const before = await fetchWebhook(webhookId);
    const updated = await updateWebhook(webhookId, changes);

    console.log('Webhook Update Log:');
    console.log(`ID: ${webhookId}`);
    console.log(`Timestamp: ${new Date().toISOString()}`);
    console.log(`Reason: ${reason}`);
    console.log('Changes:', JSON.stringify(changes, null, 2));
    console.log(`Updated at: ${updated.updated_at}`);

    return updated;
    }

    // Usage
    await updateWithLog(
    'WHwebhook123',
    { enabled: false },
    'Maintenance window for server upgrade'
    );
  • Event Filter Replacement: Understand behavior

    • enabled_events replaces entire array
    • Not merged with existing events
    • Fetch current events first if adding to existing
    • Empty array = subscribe to all events

Common Workflows

Maintenance Mode

// Disable webhook before maintenance
async function maintenanceMode(webhookId, enable = false) {
if (!enable) {
console.log('Entering maintenance mode...');
await updateWebhook(webhookId, { enabled: false });
console.log('✓ Webhook disabled');
console.log('Events will not be sent until re-enabled');
} else {
console.log('Exiting maintenance mode...');
await updateWebhook(webhookId, { enabled: true });
console.log('✓ Webhook re-enabled');
console.log('Events will resume');
}
}

// Usage
await maintenanceMode('WHwebhook123', false); // Enter maintenance
// ... perform maintenance ...
await maintenanceMode('WHwebhook123', true); // Exit maintenance

URL Migration with Verification

// Safely migrate to new URL
async function safeUrlMigration(webhookId, newUrl) {
console.log('Starting webhook URL migration...');

// 1. Fetch current config
const current = await fetchWebhook(webhookId);
console.log(`Current URL: ${current.url}`);

// 2. Test new URL is accessible
console.log(`Testing ${newUrl}...`);
try {
const testResponse = await fetch(newUrl, { method: 'HEAD' });
if (!testResponse.ok) {
throw new Error(`New URL returned ${testResponse.status}`);
}
} catch (error) {
console.error(`✗ New URL not accessible: ${error.message}`);
return null;
}
console.log('✓ New URL is accessible');

// 3. Update webhook
const updated = await updateWebhook(webhookId, {
url: newUrl
});
console.log(`✓ Webhook updated to ${updated.url}`);

// 4. Verify update
const verified = await fetchWebhook(webhookId);
if (verified.url !== newUrl) {
console.error('✗ URL update failed verification');
return null;
}

console.log('✓ Migration verified successfully');
console.log('Monitor new endpoint for incoming events');

return updated;
}

Progressive Event Filtering

// Gradually filter events to reduce noise
async function progressiveFiltering(webhookId) {
// Start with all events
console.log('Step 1: Subscribing to all events (baseline)');
await updateWebhook(webhookId, {
enabled_events: []
});

// After monitoring, filter to payment-related
console.log('\nStep 2: Filter to payment events');
await new Promise(resolve => setTimeout(resolve, 3000)); // Simulate time passing
await updateWebhook(webhookId, {
enabled_events: [
{ entity: 'transfer', types: [] }, // All transfer events
{ entity: 'settlement', types: [] } // All settlement events
]
});

// Further refine to critical only
console.log('\nStep 3: Further refine to critical events');
await new Promise(resolve => setTimeout(resolve, 3000));
await updateWebhook(webhookId, {
enabled_events: [
{ entity: 'transfer', types: ['succeeded', 'failed'] },
{ entity: 'settlement', types: ['funding_transfer.succeeded', 'funding_transfer.failed'] }
]
});

console.log('\n✓ Progressive filtering complete');
}

Credential Rotation Schedule

// Rotate credentials on schedule
async function rotateCredentialsMonthly(webhookId) {
console.log('Monthly credential rotation...');

// Generate new token (example)
const newToken = `prod-token-${Date.now()}-${Math.random().toString(36)}`;

// Update webhook
await updateWebhook(webhookId, {
authentication: {
type: 'BEARER',
bearer: {
token: newToken
}
}
});

console.log('✓ Credentials rotated');
console.log('Next rotation:', new Date(Date.now() + 30*24*60*60*1000));

// Store new token securely
await storeToken(webhookId, newToken);

return newToken;
}

Security Considerations

  • HTTPS Required: URL validation

    • Must start with https://
    • Valid SSL certificate required
    • No self-signed certificates
    • Update will fail for HTTP URLs
  • Authentication Updates: Immediate effect

    • New credentials used immediately
    • Update your endpoint first
    • Then update webhook
    • Test thoroughly
  • URL Changes: Verify ownership

    • Ensure you control new URL
    • Test before updating
    • Monitor for failed deliveries
    • Can't undo easily
  • Audit Changes: Track modifications

    • Log who updated webhook
    • Log what changed
    • Log when and why
    • updated_at shows last change
  • Disable vs Delete: Use disable

    • Disable preserves configuration
    • Can re-enable anytime
    • Delete loses signing key
    • Delete loses configuration

Error Responses

Webhook Not Found

{
"status": 404,
"message": "Webhook not found"
}
  • Invalid webhook ID
  • Webhook deleted
  • Wrong environment

Invalid URL

{
"status": 400,
"message": "Invalid URL",
"details": "URL must use HTTPS protocol"
}
  • URL not HTTPS
  • Invalid URL format
  • Use https:// prefix

Invalid Authentication

{
"status": 400,
"message": "Invalid authentication configuration",
"details": "Basic authentication requires username and password"
}
  • Missing required auth fields
  • Invalid auth type
  • Check authentication object structure

Invalid Events

{
"status": 400,
"message": "Invalid enabled_events",
"details": "Unknown entity type: invalid_entity"
}
  • Invalid entity name
  • Invalid event type
  • Check spelling and format

Troubleshooting

Update Not Applied

  • Check response for errors
  • Verify webhook ID correct
  • Fetch webhook to confirm
  • Check updated_at timestamp

Events Not Arriving at New URL

  • Verify URL accessible from internet
  • Check endpoint returns 200 OK
  • Verify authentication configured correctly
  • Test signature verification

Can't Update Signing Key

  • By design - read-only
  • Set only on creation
  • Create new webhook if needed
  • Cannot change after creation

Accidentally Disabled Webhook

// Quickly re-enable
await updateWebhook('WHwebhook123', { enabled: true });
  • POST /webhooks: Create new webhook
  • GET /webhooks: List all webhooks
  • GET /webhooks/{id}: Fetch webhook details