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
| Parameter | Type | Required | Description |
|---|---|---|---|
| webhook_id | string | Yes | The ID of the Webhook to update |
| url | string | No | New HTTPS endpoint URL |
| enabled | boolean | No | Enable (true) or disable (false) webhook |
| authentication | object | No | Authentication configuration |
| authentication.type | string | No | "NONE", "BASIC", or "BEARER" |
| authentication.basic | object | No | Required if type is "BASIC" |
| authentication.basic.username | string | No | HTTP Basic Auth username |
| authentication.basic.password | string | No | HTTP Basic Auth password |
| authentication.bearer | object | No | Required if type is "BEARER" |
| authentication.bearer.token | string | No | Bearer token |
| enabled_events | array | No | Event filters (empty array = all events) |
| enabled_events[].entity | string | No | Entity type (transfer, merchant, etc.) |
| enabled_events[].types | array | No | Event 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_keyis 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 eventsfalse: 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)
- Empty array
-
Updated Timestamp: Changes on update
updated_atreflects last modificationcreated_atnever 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_eventsreplaces 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_atshows 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_attimestamp
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 });
Related Endpoints
- POST /webhooks: Create new webhook
- GET /webhooks: List all webhooks
- GET /webhooks/{id}: Fetch webhook details