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

EventDescriptionTriggered When
message.queuedMessage accepted for processingMessage enters queue
message.processingMessage being processedProcessing starts
message.sentMessage transmitted to partnerHTTP transmission succeeds
message.deliveredMDN received from partnerPositive MDN received
message.failedMessage transmission failedTransmission or processing fails
message.receivedInbound message receivedPartner sends message to you

Certificate Events

EventDescriptionTriggered When
certificate.uploadedNew certificate addedCertificate upload completes
certificate.expires_soonCertificate expiring30 days before expiry
certificate.expiredCertificate has expiredCertificate expires
certificate.validation_failedCertificate validation failedInvalid certificate uploaded

Partner Events

EventDescriptionTriggered When
partner.createdNew partner addedPartner creation completes
partner.updatedPartner configuration changedPartner settings modified
partner.deactivatedPartner deactivatedPartner status changed to inactive
partner.connection_failedPartner connection issuesConnection test fails

Billing Events

EventDescriptionTriggered When
billing.usage.postedUsage reported to StripeHourly usage sync
billing.payment_succeededPayment processed successfullyStripe payment succeeds
billing.payment_failedPayment processing failedStripe payment fails
billing.subscription.updatedSubscription changedPlan or status change

Special Events

EventDescriptionTriggered When
*All eventsAny event occurs
test.webhookTest eventManual 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

  1. Immediate: First delivery attempt
  2. 5 minutes: First retry (if failed)
  3. 15 minutes: Second retry
  4. 45 minutes: Third retry
  5. 2.25 hours: Fourth retry
  6. 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

Next Steps