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 |
user.screened | Triggered when a user has completed risk screening |
user.status.block | Triggered when a user status has changed to blocked |
user.status.unblock | Triggered when a user status has changed from blocked to active |
payment_instrument.screened | Triggered when a payment instrument has completed risk screening |
event.screened | Triggered when an event has completed risk screening |
merchant.screened | Triggered when a merchant has completed risk screening |
alert.created | Triggered when an alert is created for a transaction |
alert.reviewed | Triggered when an alert has been reviewed |
transaction.confirmedFraud | Triggered when a transaction is confirmed as fraudulent |
transaction.confirmedChargeback | Triggered when a transaction is confirmed as a chargeback |
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",
"user.screened"
],
"secret": "whsec_8fe59a8886bb4a31a54339c25a57c286"
}Example Response
{
"status": "success",
"subscription": {
"id": "whsub_123456789",
"url": "https://example.com/webhooks/orca-events",
"events": [
"transaction.screened",
"user.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": "whsub_123456789",
"url": "https://example.com/webhooks/orca-events",
"events": [
"transaction.screened",
"user.screened"
]
},
{
"id": "whsub_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
Webhook payloads vary depending on the event type but follow a consistent structure.
Example Payload: transaction.screened
The payload field contains the standard TransactionResponse object.
{
"event": "transaction.screened",
"timestamp": 1734167723000,
"payload": {
"id": "TXN123456",
"riskLevel": "high",
"recommendedAction": "REVIEW",
"triggered": [
{
"id": "RULE123",
"name": "High Value Transaction",
"level": "high"
}
]
}
}Example Payload: alert.created
The payload field contains the alert specific result within the triggered array of the standard TransactionResponse object for the transaction that triggered the alert.
As there may be multiple alerts per transaction, the transactionId and ruleId can be used as a unique identifier of the alert.
{
"event": "alert.created",
"payload": {
"transactionId": "f55c3c6b-eb60-4ce8-9aab-375e7cd29a49",
"ruleId": "TtGnpT9XpKelFp8LfRis",
"ruleName": "High Value Transaction",
"level": "high",
"reason": "Value of transaction exceeded cumulative threshold",
"recommendedAction": "BLOCK",
"alertedAt": 1778753730995
},
"timestamp": 1778753730995
}Example Payload: alert.reviewed
The payload field contains the details about the alert reviewed on the dashboard.
{
"event": "alert.reviewed",
"payload": {
"transactionId": "f55c3c6b-eb60-4ce8-9aab-375e7cd29a49",
"ruleId": "Qu86Bbv9JRg7aC0dgONO",
"status": "cleared",
"reviewedBy": "Jane Smith",
"reviewedAt": 1778766922441,
"feedback": "Verified with customer"
},
"timestamp": 1778766929665
}Example Payload: transaction.confirmedFraud
The payload field contains details about a transaction should it be manually marked as fraudulent on the dashboard
{
"event": "transaction.confirmedFraud",
"payload": {
"transactionId": "b18dd064-86fe-4f9f-9d73-d96e896f1cb7",
"feedback": "This transaction is fraudulent",
"confirmedBy": "Jane Smith",
"confirmedAt": 1734167723000
},
"timestamp": 1734167723000
}Example Payload: transaction.confirmedChargeback
The payload field contains details about a transaction should it be manually marked as a chargeback on the dashboard
{
"event": "transaction.confirmedChargeback",
"payload": {
"transactionId": "b18dd064-86fe-4f9f-9d73-d96e896f1cb7",
"feedback": "We received a chargeback for this transaction",
"confirmedBy": "Jane Smith",
"confirmedAt": 1734167723000
},
"timestamp": 1734167723000
}Example Payload: user.status.block
{
"event": "user.status.block",
"payload": {
"id": "USR123456",
"status": "blocked",
"reason": "Comment: Suspicious activity detected Reviewed by: Jane Smith",
"updatedAt": 1734167723000
},
"timestamp": 1734167723000
}Example Payload: user.status.unblock
{
"event": "user.status.unblock",
"payload": {
"id": "USR123456",
"status": "active",
"reason": "Comment: Verified with customer Reviewed by: Jane Smith",
"updatedAt": 1734167723000
},
"timestamp": 1734167723000
}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.