Webhooks
HTTP callbacks for async execution completion
Webhooks
Reliable callback delivery with HMAC signatures and automatic retries
HTTP callbacks that notify your application when async executions complete. Agentfield POSTs execution results to your endpoint with HMAC-SHA256 signatures for verification.
Configuration
Register a webhook when queuing async execution:
curl -X POST http://localhost:8080/api/v1/execute/async/research-agent.analyze_market \
-H "Content-Type: application/json" \
-d '{
"input": {
"market": "autonomous software",
"depth": "comprehensive"
},
"webhook": {
"url": "https://app.example.com/research/complete",
"secret": "your-webhook-secret",
"headers": {
"X-Custom-ID": "research-123"
}
}
}'Response:
{
"execution_id": "exec_abc123",
"workflow_id": "wf_xyz789",
"run_id": "run_def456",
"status": "queued",
"webhook_registered": true,
"webhook_error": null
}Webhook Payload
When execution completes, Agentfield POSTs to your webhook URL:
{
"event": "execution.completed",
"execution_id": "exec_abc123",
"workflow_id": "wf_xyz789",
"status": "succeeded",
"target": "research-agent.analyze_market",
"type": "reasoner",
"duration_ms": 45230,
"result": {
"market_size": "$2.3B",
"growth_rate": "47% YoY",
"key_players": [...],
"recommendation": "high_potential"
},
"timestamp": "2024-01-15T10:31:30Z"
}Failed deliveries use the same shape but include error_message:
{
"event": "execution.failed",
"execution_id": "exec_def456",
"workflow_id": "wf_xyz789",
"status": "failed",
"target": "research-agent.analyze_market",
"type": "reasoner",
"error_message": "Upstream service unavailable",
"timestamp": "2024-01-15T10:31:30Z"
}Event Types:
execution.completed— Emitted when the execution finished successfullyexecution.failed— Emitted for any non-successful terminal state (failed, cancelled, timeout, etc.). Inspect thestatusand optionalerror_messagefields for details.
Prop
Type
Signature Verification
Agentfield signs webhook payloads with HMAC-SHA256 whenever you provide a webhook secret. Verify signatures to ensure requests are authentic.
Signature Header:
X-Agentfield-Signature: sha256=a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3Verification Examples:
import hmac
import hashlib
def verify_webhook(request):
signature = request.headers.get('X-Agentfield-Signature', '')
if not signature.startswith('sha256='):
return False
expected_sig = signature[7:] # Remove 'sha256=' prefix
secret = os.getenv('WEBHOOK_SECRET').encode()
body = request.get_data()
computed_sig = hmac.new(
secret,
body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(computed_sig, expected_sig)
@app.route('/webhook', methods=['POST'])
async def handle_webhook():
if not verify_webhook(request):
return {'error': 'Invalid signature'}, 401
payload = request.json
# Process webhook...import crypto from 'crypto';
function verifyWebhook(req) {
const signature = req.headers['x-agentfield-signature'] || '';
if (!signature.startsWith('sha256=')) {
return false;
}
const expectedSig = signature.substring(7);
const secret = process.env.WEBHOOK_SECRET;
const body = JSON.stringify(req.body);
const computedSig = crypto
.createHmac('sha256', secret)
.update(body)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(computedSig),
Buffer.from(expectedSig)
);
}
app.post('/webhook', (req, res) => {
if (!verifyWebhook(req)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const payload = req.body;
// Process webhook...
});import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"strings"
)
func verifyWebhook(signature string, body []byte, secret string) bool {
if !strings.HasPrefix(signature, "sha256=") {
return false
}
expectedSig := signature[7:]
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(body)
computedSig := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(computedSig), []byte(expectedSig))
}
func handleWebhook(w http.ResponseWriter, r *http.Request) {
signature := r.Header.Get("X-Agentfield-Signature")
body, _ := io.ReadAll(r.Body)
if !verifyWebhook(signature, body, os.Getenv("WEBHOOK_SECRET")) {
http.Error(w, "Invalid signature", http.StatusUnauthorized)
return
}
// Process webhook...
}Security Best Practices:
- Always verify signatures before processing webhooks
- Treat webhooks without
X-Agentfield-Signatureas unauthenticated (no secret was supplied) - Use environment variables for webhook secrets
- Use
hmac.compare_digest()orcrypto.timingSafeEqual()to prevent timing attacks - Validate payload structure before processing
- Log webhook failures for debugging
Delivery Guarantees
Agentfield retries failed deliveries with exponential backoff. Defaults come from agentfield.execution_queue in agentfield.yaml and can be tuned.
- Max Attempts: 3 total attempts by default (initial send + retries)
- Backoff: Starts at 1s and doubles each retry up to 5s by default
- Timeout: 10 seconds per attempt
- Retry policy: Any non-2xx response or transport error is retried until attempts are exhausted
Webhook Event Logs:
Agentfield stores webhook delivery attempts in the database. Check execution details in the UI to see:
- Delivery status (delivered/failed)
- HTTP status code
- Response body (truncated)
- Retry attempts
Related
- Async Execution - Queue long-running tasks
- REST API - HTTP endpoints for execution and memory
- Server-Sent Events - Real-time execution updates