Appearance
Webhooks ​
Webhooks enable MFTPlus to push real-time notifications to your application when important events occur. Instead of polling the API for status updates, configure webhooks to receive HTTP callbacks as events happen.
Overview ​
When an event occurs in your MFTPlus deployment (such as a transfer completing or an agent going offline), MFTPlus sends an HTTP POST request to a URL you configure. Your application can then process these events to trigger workflows, update databases, or send notifications.
Use Cases ​
- Transfer Notifications: Alert teams when file transfers complete or fail
- Agent Monitoring: Trigger alerts when agents go offline or certificates expire
- Audit Integration: Stream events to SIEM systems for compliance
- Automation: Chain additional workflows based on transfer status
- Custom Notifications: Send alerts to Slack, Microsoft Teams, or other services
Configuration ​
Configure webhooks through the MFTPlus admin panel or API.
Admin Panel Configuration ​
- Navigate to Settings → Webhooks
- Click Add Webhook
- Configure webhook properties:
- Name: Descriptive label for this webhook
- URL: Endpoint that will receive POST requests
- Events: Select which events trigger this webhook
- Secret: (Optional) HMAC signature key for security
- Active: Enable/disable the webhook
API Configuration ​
Create a webhook via the REST API:
bash
curl -X POST https://api.mftplus.co.za/api/webhooks \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <admin-token>" \
-d '{
"name": "Transfer Alerts",
"url": "https://your-app.com/webhooks/mftplus",
"events": ["transfer.completed", "transfer.failed"],
"secret": "your-signing-secret",
"active": true
}'Event Types ​
MFTPlus sends webhook notifications for the following events:
Transfer Events ​
| Event | Description | Trigger |
|---|---|---|
transfer.created | A new transfer was initiated | Transfer job starts |
transfer.progress | Transfer progress update | Every 10% progress |
transfer.completed | Transfer completed successfully | File transfer finishes |
transfer.failed | Transfer failed | Error during transfer |
transfer.cancelled | Transfer was cancelled | Manual or automatic cancellation |
Agent Events ​
| Event | Description | Trigger |
|---|---|---|
agent.registered | New agent registered | Agent joins deployment |
agent.connected | Agent came online | Agent connects after offline |
agent.disconnected | Agent went offline | Agent loses connection |
agent.certificate.expiring | Certificate expiring soon | 30 days before expiration |
agent.certificate.expired | Certificate expired | Certificate no longer valid |
Job Events ​
| Event | Description | Trigger |
|---|---|---|
job.started | Scheduled job started | Job execution begins |
job.completed | Job completed successfully | All transfers in job finish |
job.failed | Job failed | One or more transfers fail |
job.skipped | Job skipped | Schedule conditions not met |
System Events ​
| Event | Description | Trigger |
|---|---|---|
system.health.degraded | System performance degraded | Resource thresholds exceeded |
system.storage.warning | Storage capacity warning | Disk usage above 80% |
system.error | System error occurred | Unexpected system failure |
Payload Format ​
All webhook payloads follow this structure:
json
{
"id": "whevt_1a2b3c4d5e6f7g8h",
"event": "transfer.completed",
"timestamp": "2026-04-29T15:30:00Z",
"data": {
// Event-specific data
}
}Transfer Event Payloads ​
transfer.completed:
json
{
"id": "whevt_1a2b3c4d5e6f7g8h",
"event": "transfer.completed",
"timestamp": "2026-04-29T15:30:00Z",
"data": {
"transferId": "trf_1a2b3c4d5e6f",
"agentId": "agent_1a2b3c4d",
"agentName": "production-server-1",
"sourceUrl": "sftp://ftp.example.com/files/data.csv",
"destinationPath": "/local/data.csv",
"direction": "pull",
"protocol": "sftp",
"size": 1048576,
"bytesTransferred": 1048576,
"duration": 45,
"speed": 23303,
"status": "completed"
}
}transfer.failed:
json
{
"id": "whevt_2b3c4d5e6f7g8h9i",
"event": "transfer.failed",
"timestamp": "2026-04-29T15:31:00Z",
"data": {
"transferId": "trf_2b3c4d5e6f",
"agentId": "agent_1a2b3c4d",
"agentName": "production-server-1",
"sourceUrl": "sftp://ftp.example.com/files/data.csv",
"error": "Authentication failed: Invalid credentials",
"errorCode": "AUTH_FAILED",
"retryCount": 3,
"status": "failed"
}
}Agent Event Payloads ​
agent.disconnected:
json
{
"id": "whevt_3c4d5e6f7g8h9i0j",
"event": "agent.disconnected",
"timestamp": "2026-04-29T15:32:00Z",
"data": {
"agentId": "agent_1a2b3c4d",
"agentName": "production-server-1",
"hostname": "server1.example.com",
"lastSeen": "2026-04-29T15:30:00Z",
"status": "offline"
}
}agent.certificate.expiring:
json
{
"id": "whevt_4d5e6f7g8h9i0j1k",
"event": "agent.certificate.expiring",
"timestamp": "2026-04-29T15:33:00Z",
"data": {
"agentId": "agent_1a2b3c4d",
"agentName": "production-server-1",
"certificateId": "cert_1a2b3c4d",
"expiresAt": "2026-05-29T00:00:00Z",
"daysRemaining": 30
}
}Job Event Payloads ​
job.completed:
json
{
"id": "whevt_5e6f7g8h9i0j1k2l",
"event": "job.completed",
"timestamp": "2026-04-29T15:34:00Z",
"data": {
"jobId": "job_1a2b3c4d",
"jobName": "Daily Backup",
"agentId": "agent_1a2b3c4d",
"schedule": "0 2 * * *",
"transfersTotal": 5,
"transfersCompleted": 5,
"transfersFailed": 0,
"duration": 320,
"status": "completed"
}
}Security ​
Signature Verification ​
Each webhook request includes an X-Webhook-Signature header when a secret is configured. Verify this signature to ensure the request originated from MFTPlus.
The signature is an HMAC hex digest computed using the SHA-256 algorithm:
X-Webhook-Signature: sha256=<signature>Verification Example (Node.js):
javascript
import crypto from 'crypto';
function verifySignature(payload, signature, secret) {
const hmac = crypto.createHmac('sha256', secret);
const digest = hmac.update(payload).digest('hex');
const expectedSignature = `sha256=${digest}`;
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
// Usage
const payload = request.rawBody;
const signature = request.headers['x-webhook-signature'];
const isValid = verifySignature(payload, signature, 'your-secret');Verification Example (Python):
python
import hmac
import hashlib
def verify_signature(payload, signature, secret):
expected_signature = 'sha256=' + hmac.new(
secret.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected_signature)
# Usage
payload = request.body
signature = request.headers.get('X-Webhook-Signature')
is_valid = verify_signature(payload, signature, 'your-secret')HTTPS Requirements ​
- Always use HTTPS URLs for webhook endpoints
- MFTPlus refuses to send webhooks to HTTP endpoints (except for localhost testing)
- Ensure your SSL certificate is valid and not expired
IP Whitelisting ​
For additional security, configure your firewall to only accept webhook connections from MFTPlus IP addresses. Contact support for the current IP ranges.
Retry Policy ​
When your webhook endpoint returns a non-2xx status code or times out, MFTPlus retries delivery:
| Attempt | Timing |
|---|---|
| 1st retry | 1 minute after failure |
| 2nd retry | 5 minutes after 1st retry |
| 3rd retry | 30 minutes after 2nd retry |
| 4th retry | 2 hours after 3rd retry |
After 4 failed attempts, the webhook is marked as failed and an alert is sent to the admin. Manual reactivation is required.
Timeout: Webhook endpoints must respond within 10 seconds. Requests timing out are retried.
Testing ​
Testing Webhooks Locally ​
Use localtunnel or ngrok to test webhooks during development:
bash
# Using localtunnel
npx localtunnel --port 3000
# Using ngrok
ngrok http 3000Then configure your webhook with the provided HTTPS URL.
Test Event Payloads ​
Send a test event from the admin panel:
- Navigate to Settings → Webhooks
- Select your webhook
- Click Send Test Event
- Choose an event type to simulate
Best Practices ​
Endpoint Design ​
- Return quickly: Process webhooks asynchronously and respond immediately
- Return 200 OK: Always return 200-299 status, even for internal errors
- Idempotency: Handle duplicate events (use the
idfield for deduplication) - Logging: Log all received webhooks for troubleshooting
Example Handler (Node.js/Express):
javascript
app.post('/webhooks/mftplus', async (req, res) => {
const signature = req.headers['x-webhook-signature'];
// Verify signature
if (!verifySignature(req.rawBody, signature, WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const { id, event, data } = req.body;
// Check for duplicate
if (await Event.exists({ id })) {
return res.status(200).send('Duplicate');
}
// Process asynchronously
processEvent(event, data).catch(console.error);
// Save event ID for deduplication
await Event.create({ id, event, data });
// Respond immediately
res.status(200).send('OK');
});Security Checklist ​
- [ ] Always use HTTPS endpoints
- [ ] Configure and verify signature secrets
- [ ] Implement request deduplication
- [ ] Validate payload structure
- [ ] Log all webhook events
- [ ] Monitor failed webhook deliveries
- [ ] Rotate secrets periodically
Monitoring ​
Monitor webhook health through the admin panel:
- Delivery Status: Success/failure rates
- Response Times: Average endpoint response time
- Recent Failures: Last 10 failed deliveries with error details
Set up alerts for:
- Failed webhooks (after all retries exhausted)
- High failure rates (>5% in last hour)
- Slow endpoint responses (>5 seconds)
Troubleshooting ​
Webhooks Not Arriving ​
- Check webhook status: Verify webhook is Active in admin panel
- Verify URL: Ensure endpoint URL is correct and accessible
- Check firewall rules: Confirm MFTPlus IPs can reach your endpoint
- Review signature verification: Temporarily disable to test
- Check delivery logs: View recent delivery attempts in admin panel
Signature Verification Failing ​
- Confirm secret matches: Verify secret in webhook configuration
- Check raw payload: Ensure you're using the raw request body, not parsed
- Verify algorithm: Confirm SHA-256 HMAC calculation
- Test without verification: Temporarily disable to isolate the issue
High Failure Rates ​
- Check endpoint health: Verify your application is running
- Review response times: Ensure responses are under 10 seconds
- Monitor errors: Check application logs for exceptions during processing
- Scale infrastructure: Add capacity if endpoint is overloaded
Duplicate Events ​
Duplicate deliveries are normal and expected. Use the id field in each payload to deduplicate:
javascript
const processedEvents = new Set();
function handleWebhook(payload) {
if (processedEvents.has(payload.id)) {
return; // Skip duplicate
}
processedEvents.add(payload.id);
// Process event...
}Managing Webhooks ​
List Webhooks ​
bash
curl https://api.mftplus.co.za/api/webhooks \
-H "Authorization: Bearer <admin-token>"Get Webhook Details ​
bash
curl https://api.mftplus.co.za/api/webhooks/{id} \
-H "Authorization: Bearer <admin-token>"Update Webhook ​
bash
curl -X PATCH https://api.mftplus.co.za/api/webhooks/{id} \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <admin-token>" \
-d '{
"url": "https://new-url.com/webhook",
"active": false
}'Delete Webhook ​
bash
curl -X DELETE https://api.mftplus.co.za/api/webhooks/{id} \
-H "Authorization: Bearer <admin-token>"View Delivery History ​
bash
curl https://api.mftplus.co.za/api/webhooks/{id}/deliveries \
-H "Authorization: Bearer <admin-token>"Example Integrations ​
Slack Notifications ​
javascript
app.post('/webhooks/mftplus', (req, res) => {
const { event, data } = req.body;
if (event === 'transfer.failed') {
axios.post(process.env.SLACK_WEBHOOK_URL, {
text: `Transfer failed: ${data.sourceUrl}`,
attachments: [{
color: 'danger',
fields: [
{ title: 'Agent', value: data.agentName },
{ title: 'Error', value: data.error }
]
}]
});
}
res.status(200).send('OK');
});Database Update ​
python
@app.route('/webhooks/mftplus', methods=['POST'])
def webhook_handler():
payload = request.get_json()
if payload['event'] == 'transfer.completed':
# Update transfer record in database
db.transfers.update_one(
{'transfer_id': payload['data']['transferId']},
{'$set': {
'status': 'completed',
'completed_at': payload['timestamp']
}}
)
return '', 200PagerDuty Alert ​
javascript
app.post('/webhooks/mftplus', async (req, res) => {
const { event, data } = req.body;
if (event === 'agent.disconnected') {
await axios.post(
`https://api.pagerduty.com/incidents`,
{
incident: {
type: 'incident',
title: `MFTPlus Agent Offline: ${data.agentName}`,
service: { id: process.env.PAGERDUTY_SERVICE_ID },
urgency: 'high'
}
},
{
headers: {
'Authorization': `Token token=${process.env.PAGERDUTY_TOKEN}`,
'Accept': 'application/vnd.pagerduty+json;version=2'
}
}
);
}
res.status(200).send('OK');
});Reference ​
Request Headers ​
| Header | Description |
|---|---|
Content-Type | Always application/json |
X-Webhook-Signature | HMAC SHA-256 signature (if secret configured) |
X-Webhook-ID | Unique webhook delivery ID |
X-Webhook-Timestamp | Unix timestamp of delivery attempt |
User-Agent | MFTPlus-Webhook/1.0 |
Response Requirements ​
- Status Code: Return 2xx for successful processing
- Body: Optional (ignored by MFTPlus)
- Timeout: Respond within 10 seconds to avoid retry
Common Errors ​
| HTTP Status | Description | Retry Behavior |
|---|---|---|
| 401 | Invalid signature | No retry (security issue) |
| 404 | Endpoint not found | Retry (may be temporary) |
| 408 | Request timeout | Retry |
| 429 | Rate limited | Retry with exponential backoff |
| 500+ | Server error | Retry |
For more information, see API Reference or Troubleshooting.