Webhooks
Webhooks provide real-time notifications about events in your AS2aaS account. Set up webhook endpoints to receive instant updates about message status, certificate expiration, billing events, and more.
Webhook Overview
AS2aaS webhooks deliver event notifications to your application via HTTP POST requests. This enables:
- Real-time Updates: Instant notification when events occur
- Automated Workflows: Trigger business processes based on AS2 events
- Monitoring & Alerting: Build custom monitoring dashboards
- Integration: Connect AS2aaS with your existing systems
Creating Webhook Endpoints
Basic Webhook Endpoint
curl -X POST https://api.as2aas.com/v1/webhook-endpoints \
-H "Authorization: Bearer pk_test_your_api_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"url": "https://your-app.com/webhooks/as2",
"events": [
"message.sent",
"message.delivered",
"message.failed"
],
"description": "Production webhook endpoint"
}'
Advanced Webhook Configuration
curl -X POST https://api.as2aas.com/v1/webhook-endpoints \
-H "Authorization: Bearer pk_test_your_api_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"url": "https://your-app.com/webhooks/as2",
"events": ["*"],
"description": "Comprehensive webhook endpoint",
"secret": "whsec_your_secret_key",
"active": true,
"retry_attempts": 5,
"retry_interval": 300,
"timeout": 30,
"headers": {
"X-Custom-Header": "custom-value",
"Authorization": "Bearer custom-token"
}
}'
Available Events
Message Events
Event | Description | Triggered When |
---|---|---|
message.queued | Message accepted for processing | Message enters queue |
message.processing | Message being processed | Processing starts |
message.sent | Message transmitted to partner | HTTP transmission succeeds |
message.delivered | MDN received from partner | Positive MDN received |
message.failed | Message transmission failed | Transmission or processing fails |
message.received | Inbound message received | Partner sends message to you |
Certificate Events
Event | Description | Triggered When |
---|---|---|
certificate.uploaded | New certificate added | Certificate upload completes |
certificate.expires_soon | Certificate expiring | 30 days before expiry |
certificate.expired | Certificate has expired | Certificate expires |
certificate.validation_failed | Certificate validation failed | Invalid certificate uploaded |
Partner Events
Event | Description | Triggered When |
---|---|---|
partner.created | New partner added | Partner creation completes |
partner.updated | Partner configuration changed | Partner settings modified |
partner.deactivated | Partner deactivated | Partner status changed to inactive |
partner.connection_failed | Partner connection issues | Connection test fails |
Billing Events
Event | Description | Triggered When |
---|---|---|
billing.usage.posted | Usage reported to Stripe | Hourly usage sync |
billing.payment_succeeded | Payment processed successfully | Stripe payment succeeds |
billing.payment_failed | Payment processing failed | Stripe payment fails |
billing.subscription.updated | Subscription changed | Plan or status change |
Special Events
Event | Description | Triggered When |
---|---|---|
* | All events | Any event occurs |
test.webhook | Test event | Manual webhook test |
Webhook Payload Structure
Standard Payload Format
{
"id": "evt_1234567890",
"type": "message.delivered",
"created_at": "2024-01-15T10:35:15Z",
"tenant_id": "tenant_123",
"data": {
"id": "msg_9876543210",
"partner_id": "prt_000001",
"status": "delivered",
"subject": "Invoice INV-001",
"delivered_at": "2024-01-15T10:35:15Z"
},
"previous_attributes": {
"status": "sent"
}
}
Message Event Payload
{
"id": "evt_msg_delivered_123",
"type": "message.delivered",
"created_at": "2024-01-15T10:35:15Z",
"tenant_id": "tenant_123",
"data": {
"id": "msg_9876543210",
"partner_id": "prt_000001",
"direction": "outbound",
"status": "delivered",
"subject": "Invoice INV-001",
"content_type": "application/edi-x12",
"message_id": "[email protected]",
"mdn_status": "received",
"attempts": 1,
"created_at": "2024-01-15T10:35:00Z",
"sent_at": "2024-01-15T10:35:05Z",
"delivered_at": "2024-01-15T10:35:15Z"
}
}
Certificate Event Payload
{
"id": "evt_cert_expires_123",
"type": "certificate.expires_soon",
"created_at": "2024-01-15T10:00:00Z",
"tenant_id": "tenant_123",
"data": {
"id": "cert_1234567890",
"name": "Company Signing Certificate",
"type": "signing",
"subject": "CN=Company Name, O=Company Inc",
"expires_at": "2024-02-15T23:59:59Z",
"days_until_expiry": 30,
"fingerprint": "SHA256:ab:cd:ef:12:34:56:78:90"
}
}
Webhook Security
HMAC Signature Verification
All webhook payloads are signed with HMAC-SHA256 using your webhook secret:
Headers:
AS2aaS-Signature: sha256=a0c0d5e1f2a3b4c5d6e7f8g9h0i1j2k3l4m5n6o7p8q9r0s1t2u3v4w5x6y7z8a9
AS2aaS-Timestamp: 1642262400
AS2aaS-Event-Type: message.delivered
Signature Verification Examples
Node.js
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const hmac = crypto.createHmac('sha256', secret);
hmac.update(payload, 'utf8');
const digest = 'sha256=' + hmac.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(digest)
);
}
// Express.js webhook handler
app.post('/webhooks/as2', express.raw({type: 'application/json'}), (req, res) => {
const signature = req.headers['as2aas-signature'];
const payload = req.body;
const secret = process.env.WEBHOOK_SECRET;
if (!verifyWebhookSignature(payload, signature, secret)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(payload);
console.log('Received event:', event.type);
res.status(200).send('OK');
});
Python
import hmac
import hashlib
import json
def verify_webhook_signature(payload, signature, secret):
expected_signature = 'sha256=' + hmac.new(
secret.encode('utf-8'),
payload.encode('utf-8'),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected_signature)
# Flask webhook handler
@app.route('/webhooks/as2', methods=['POST'])
def handle_webhook():
signature = request.headers.get('AS2aaS-Signature')
payload = request.get_data(as_text=True)
secret = os.environ.get('WEBHOOK_SECRET')
if not verify_webhook_signature(payload, signature, secret):
return 'Invalid signature', 401
event = json.loads(payload)
print(f'Received event: {event["type"]}')
return 'OK', 200
PHP
function verifyWebhookSignature($payload, $signature, $secret) {
$expectedSignature = 'sha256=' . hash_hmac('sha256', $payload, $secret);
return hash_equals($signature, $expectedSignature);
}
// Webhook handler
$signature = $_SERVER['HTTP_AS2AAS_SIGNATURE'];
$payload = file_get_contents('php://input');
$secret = $_ENV['WEBHOOK_SECRET'];
if (!verifyWebhookSignature($payload, $signature, $secret)) {
http_response_code(401);
exit('Invalid signature');
}
$event = json_decode($payload, true);
error_log('Received event: ' . $event['type']);
http_response_code(200);
echo 'OK';
Webhook Endpoint Management
List Webhook Endpoints
curl -X GET https://api.as2aas.com/v1/webhook-endpoints \
-H "Authorization: Bearer pk_test_your_api_key"
Get Webhook Endpoint Details
curl -X GET https://api.as2aas.com/v1/webhook-endpoints/whep_123 \
-H "Authorization: Bearer pk_test_your_api_key"
Response:
{
"id": "whep_1234567890",
"url": "https://your-app.com/webhooks/as2",
"events": ["message.sent", "message.delivered", "message.failed"],
"active": true,
"description": "Production webhook endpoint",
"secret": "whsec_****",
"retry_attempts": 3,
"retry_interval": 300,
"timeout": 30,
"delivery_count": 15742,
"last_delivery_at": "2024-01-15T10:35:15Z",
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-01T00:00:00Z"
}
Update Webhook Endpoint
curl -X PATCH https://api.as2aas.com/v1/webhook-endpoints/whep_123 \
-H "Authorization: Bearer pk_test_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"events": ["*"],
"active": true,
"retry_attempts": 5
}'
Delete Webhook Endpoint
curl -X DELETE https://api.as2aas.com/v1/webhook-endpoints/whep_123 \
-H "Authorization: Bearer pk_test_your_api_key"
Testing Webhooks
Test Webhook Delivery
curl -X POST https://api.as2aas.com/v1/webhook-endpoints/whep_123/test \
-H "Authorization: Bearer pk_test_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"event_type": "test.webhook"
}'
Test Payload:
{
"id": "evt_test_123",
"type": "test.webhook",
"created_at": "2024-01-15T10:35:15Z",
"tenant_id": "tenant_123",
"data": {
"test": true,
"webhook_endpoint_id": "whep_1234567890",
"message": "This is a test webhook delivery"
}
}
Webhook Delivery
Delivery Guarantees
- At-least-once delivery: Events may be delivered multiple times
- Retry logic: Failed deliveries are retried with exponential backoff
- Timeout handling: Requests timeout after configured period
- Status tracking: Delivery success/failure is logged
Retry Configuration
{
"retry_attempts": 5,
"retry_interval": 300, // Initial retry interval (seconds)
"retry_backoff": "exponential",
"max_retry_interval": 3600 // Maximum interval between retries
}
Delivery Timeline
- Immediate: First delivery attempt
- 5 minutes: First retry (if failed)
- 15 minutes: Second retry
- 45 minutes: Third retry
- 2.25 hours: Fourth retry
- 6.75 hours: Final retry
Webhook Event Filtering
Filter by Event Type
curl -X POST https://api.as2aas.com/v1/webhook-endpoints \
-H "Authorization: Bearer pk_test_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/messages",
"events": ["message.sent", "message.delivered", "message.failed"]
}'
Filter by Partner
curl -X POST https://api.as2aas.com/v1/webhook-endpoints \
-H "Authorization: Bearer pk_test_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/walmart",
"events": ["*"],
"filters": {
"partner_id": "prt_walmart_001"
}
}'
Filter by Status
curl -X POST https://api.as2aas.com/v1/webhook-endpoints \
-H "Authorization: Bearer pk_test_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/failures",
"events": ["message.failed"],
"filters": {
"status": "failed"
}
}'
Common Webhook Patterns
Message Status Tracking
app.post('/webhooks/as2', (req, res) => {
const event = req.body;
switch (event.type) {
case 'message.sent':
console.log(`Message ${event.data.id} sent to partner`);
updateMessageStatus(event.data.id, 'sent');
break;
case 'message.delivered':
console.log(`Message ${event.data.id} delivered successfully`);
updateMessageStatus(event.data.id, 'delivered');
notifyCustomer(event.data.id, 'delivered');
break;
case 'message.failed':
console.error(`Message ${event.data.id} failed: ${event.data.error}`);
updateMessageStatus(event.data.id, 'failed');
alertSupport(event.data.id, event.data.error);
break;
}
res.status(200).send('OK');
});
Certificate Monitoring
@app.route('/webhooks/certificates', methods=['POST'])
def handle_certificate_events():
event = request.json
if event['type'] == 'certificate.expires_soon':
cert_data = event['data']
send_expiry_alert(
cert_name=cert_data['name'],
expires_at=cert_data['expires_at'],
days_until_expiry=cert_data['days_until_expiry']
)
elif event['type'] == 'certificate.expired':
cert_data = event['data']
handle_expired_certificate(cert_data['id'])
return 'OK', 200
Billing Integration
function handleBillingWebhook($event) {
switch ($event['type']) {
case 'billing.usage.posted':
logUsageMetrics($event['data']);
break;
case 'billing.payment_failed':
suspendAccount($event['data']['tenant_id']);
notifyBilling($event['data']);
break;
case 'billing.payment_succeeded':
reactivateAccount($event['data']['tenant_id']);
break;
}
}
Webhook Debugging
Webhook Delivery Logs
curl -X GET https://api.as2aas.com/v1/webhook-endpoints/whep_123/deliveries \
-H "Authorization: Bearer pk_test_your_api_key"
Response:
{
"data": [
{
"id": "whd_1234567890",
"event_id": "evt_9876543210",
"event_type": "message.delivered",
"status": "succeeded",
"response_code": 200,
"response_body": "OK",
"attempt": 1,
"delivered_at": "2024-01-15T10:35:15Z"
},
{
"id": "whd_1234567891",
"event_id": "evt_9876543211",
"event_type": "message.failed",
"status": "failed",
"response_code": 500,
"response_body": "Internal Server Error",
"attempt": 3,
"next_retry_at": "2024-01-15T11:00:00Z"
}
]
}
Webhook Testing Tools
- ngrok: Expose local development server
- webhook.site: Online webhook testing
- Postman: API testing and mock servers
- RequestBin: Collect and inspect webhooks
Error Handling
Common Webhook Errors
Invalid URL:
{
"error": {
"type": "validation_error",
"code": "invalid_webhook_url",
"message": "Webhook URL must be a valid HTTPS endpoint"
}
}
Delivery Failure:
{
"error": {
"type": "delivery_error",
"code": "webhook_timeout",
"message": "Webhook endpoint did not respond within 30 seconds"
}
}
Authentication Failure:
{
"error": {
"type": "authentication_error",
"code": "invalid_signature",
"message": "Webhook signature verification failed"
}
}
Best Practices
Endpoint Design
- Use HTTPS for all webhook endpoints
- Implement proper signature verification
- Return 2xx status codes for successful processing
- Process webhooks idempotently
- Handle duplicate deliveries gracefully
Error Handling
- Log all webhook deliveries for debugging
- Implement proper error handling and recovery
- Use exponential backoff for retries
- Monitor webhook endpoint health
- Set up alerting for delivery failures
Security
- Verify webhook signatures on every request
- Use strong, unique secrets for each endpoint
- Implement rate limiting on webhook endpoints
- Log and monitor for suspicious activity
- Keep webhook secrets secure and rotate regularly
Performance
- Process webhooks asynchronously when possible
- Respond quickly (within 30 seconds)
- Use queuing for complex processing
- Scale webhook endpoints horizontally
- Monitor response times and success rates