Webhooks
Webhooks provide a way to receive real-time notifications when specific events occur within the Orca system. This allows your application to automatically react to events such as transactions being screened or risk assessments being completed.
We recommend that Webhooks should be used for the Time-Sensitive Transaction Monitoring.
Overview
Webhooks are HTTP callbacks that deliver data to your servers when events happen in the Orca system. They eliminate the need for continuous polling and allow your application to be notified immediately when events occur.
Event Types
The following event types are currently supported:
| Event Type | Description |
|---|---|
transaction.screened | Triggered when a transaction has completed risk screening |
merchant.screened | Triggered when a merchant has completed risk screening |
Managing Webhook Subscriptions
Create Webhook Subscription
Register a new webhook endpoint to receive event notifications.
Endpoint
POST /v1/webhooksRequest Schema
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | The URL that will receive webhook events |
events | string[] | Yes | Array of event types to subscribe to |
secret | string | Yes | Secret used to sign webhook payloads for verification |
Example Request
{
"url": "https://example.com/webhooks/orca-events",
"events": [
"transaction.screened",
"merchant.screened"
],
"secret": "whsec_8fe59a8886bb4a31a54339c25a57c286"
}Example Response
{
"status": "success",
"subscription": {
"id": "wsub_123456789",
"url": "https://example.com/webhooks/orca-events",
"events": [
"transaction.screened",
"merchant.screened"
]
}
}Responses
200: Webhook subscription created successfully400: Missing required fields or invalid format500: Internal server error
List Webhook Subscriptions
Retrieve all active webhook subscriptions for your organization.
Endpoint
GET /v1/webhooksExample Response
{
"status": "success",
"subscriptions": [
{
"id": "wsub_123456789",
"url": "https://example.com/webhooks/orca-events",
"events": [
"transaction.screened",
"merchant.screened"
]
},
{
"id": "wsub_987654321",
"url": "https://example.com/webhooks/transaction-events",
"events": [
"transaction.screened"
]
}
]
}Responses
200: Webhook subscriptions retrieved successfully500: Internal server error
Verifying Webhooks
To ensure that webhook requests are coming from Orca and haven’t been tampered with, each webhook request includes a signature. The signature is created using the secret you provided when setting up the webhook.
Signature Verification
The webhook signature is included in the X-Orca-Signature header of the webhook request with the following format:
t=1678364102,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e1e13a75To verify the signature:
- Split the header on commas to get the timestamp and signature
- Extract the timestamp after “t=”
- Extract the signature after “v1=”
- Combine the timestamp and raw response body with a period:
${timestamp}.${rawBody} - Create an HMAC-SHA256 signature using your webhook secret
- Compare the expected signature with the received signature
- Optionally verify the timestamp is recent (within 5 minutes)
Example implementation in Typescript:
import crypto from 'crypto';
/**
* Verifies the signature of an incoming webhook request
* @param req The HTTP request object
* @param secret The webhook secret for signature verification
* @returns boolean indicating if the signature is valid
*/
function verifyWebhookSignature(
req: any,
secret: string
): boolean {
try {
// Get the signature from headers
const signature = req.headers['x-orca-signature'];
if (!signature) {
console.error('Missing X-Orca-Signature header');
return false;
}
// Get raw body as string
const rawBody = typeof req.body === 'string'
? req.body
: Buffer.isBuffer(req.body)
? req.body.toString('utf8')
: JSON.stringify(req.body);
// Parse signature components
const [timeComponent, signatureComponent] = signature.split(',');
const timestamp = timeComponent.replace('t=', '');
const receivedSignature = signatureComponent.replace('v1=', '');
// Verify timestamp is recent (within 5 minutes)
const now = Date.now();
const timestampNum = parseInt(timestamp, 10);
// Compare using milliseconds (300000ms = 5 minutes)
if (Math.abs(now - timestampNum) > 300000) {
console.error('Webhook timestamp too old');
return false;
}
// Create the message to verify
const message = `${timestamp}.${rawBody}`;
// Create expected signature
const hmac = crypto.createHmac('sha256', secret);
hmac.update(message);
const expectedSignature = hmac.digest('hex');
// Compare signatures
return crypto.timingSafeEqual(
Buffer.from(receivedSignature),
Buffer.from(expectedSignature)
);
} catch (error) {
console.error('Error verifying webhook signature:', error);
return false;
}
}Example implementation in Python:
import hmac
import hashlib
import time
import base64
import json
from flask import Flask, request, abort
app = Flask(__name__)
def verify_webhook_signature(signature, raw_body, secret):
"""Verify the webhook signature against the raw body."""
try:
# Parse signature components
time_component, signature_component = signature.split(',')
timestamp = time_component.replace('t=', '')
received_signature = signature_component.replace('v1=', '')
# Verify timestamp is recent (within 5 minutes)
now = int(time.time() * 1000) # Current time in milliseconds
timestamp_num = int(timestamp)
if abs(now - timestamp_num) > 300000: # 5 minutes in milliseconds
print("Webhook timestamp too old")
return False
# Create the message to verify
message = f"{timestamp}.{raw_body}"
# Create expected signature
computed_hmac = hmac.new(
secret.encode('utf-8'),
message.encode('utf-8'),
hashlib.sha256
)
expected_signature = computed_hmac.hexdigest()
# Compare signatures using constant time comparison
return hmac.compare_digest(received_signature, expected_signature)
except Exception as e:
print(f"Error verifying webhook signature: {e}")
return False
@app.route('/webhook', methods=['POST'])
def webhook():
# Get the raw request body
raw_body = request.get_data(as_text=True)
# Get signature from header
signature = request.headers.get('X-Orca-Signature')
# Verify signature before processing
if not verify_webhook_signature(
signature,
raw_body,
secret="your_webhook_secret"
):
abort(401, "Invalid signature")
# Process the webhook payload
data = json.loads(raw_body)
print(f"Received verified webhook event: {data.get('event')}")
return "Webhook received", 200
if __name__ == "__main__":
app.run(debug=True)Webhook payloads vary depending on the event type but follow a consistent structure.
Example Payload: transaction.screened
{
"event": "transaction.screened",
"timestamp": 1734167723000,
"data": {
"id": "TXN123456",
"riskLevel": "high",
"recommendedAction": "REVIEW",
"triggered": [
{
"id": "RULE123",
"name": "High Value Transaction",
"level": "high"
}
]
}
}Best Practices
- Acknowledge webhooks quickly: Return a 2xx response as soon as possible.
- Process webhooks asynchronously: Handle the webhook data processing in the background.
- Implement idempotency: Design your webhook handler to be idempotent to avoid issues with duplicate events.
- Always verify signatures first: Verify the signature of each webhook request before parsing the body to ensure authenticity.
- Set up a retry mechanism: Be prepared to handle cases where your webhook endpoint is temporarily unavailable.