Webhooks - Real-time Financial Data Updates
Get real-time notifications the moment financial data updates. Webhooks deliver clean fundamentals to your app within minutes of disclosure.
What are Webhooks?
Webhooks allow you to receive real-time notifications when financial data is updated for companies in your coverage. Instead of polling our API repeatedly, you can configure webhook endpoints that we'll call whenever new financial statements, ratios, or other data becomes available.
Setup and Management
Adding a Webhook
- Access Organization Dashboard: Navigate to your organization dashboard through the "Manage API Keys" link
- Configure Webhook URL: Add your webhook endpoint URL that can receive HTTP POST requests
- Get Your Signing Secret: Copy your signing secret from the dashboard to verify webhook authenticity
- Enable/Disable: Toggle webhooks on or off as needed through the dashboard
Webhook URL Requirements:
Your webhook endpoint must:
- Accept HTTP POST requests
- Return HTTP status codes 200-299 for successful processing
- Be publicly accessible (no localhost or private network URLs)
- Respond within 20 seconds to avoid timeouts
Managing Webhooks
All webhook management is done through your organization dashboard:
- View Active Webhooks: See all configured webhook endpoints
- Enable/Disable: Turn webhooks on or off without deleting the configuration
- Update URLs: Modify webhook endpoints as needed
- Monitor Delivery: Track webhook delivery status and failures
Webhook Payload
When financial data or company information is updated, we'll send a POST request to your configured webhook URL with the following JSON payload:
{
"event_type": "financial_data_updated",
"timestamp": "2024-01-15T10:30:00.000Z",
"company_key": "NASDAQ_AAPL",
"statement_type": "income_statement",
"file_type": "financial_data_as_reported",
"metadata": {
"source": "fiscal",
"version": "1.0"
}
}
Payload Fields
| Field | Type | Description |
|---|---|---|
event_type | string | Type of event: company_updated, company_deleted, financial_data_updated, or financial_data_deleted |
timestamp | string | ISO 8601 timestamp when the data was updated |
company_key | string | Company identifier in EXCHANGE_TICKER format (MIC_TICKER outside of US/CA) |
statement_type | string | Type of financial statement updated (if applicable, otherwise null) |
file_type | string | Specific data type that was updated |
metadata | object | Additional metadata about the update |
Event Types
The event_type field indicates what type of change occurred:
financial_data_updated- Financial data was created or updated (most common)financial_data_deleted- Financial data was deleted or removedcompany_updated- Company profile or metadata was created or updatedcompany_deleted- Company was removed from the system
File Types
The file_type field indicates what type of data was updated:
financial_data_as_reported- any updates to as-reported or standardized financial datatemplate_metric_mapping- any additions, removals or renamings of standardized metricseps_and_waso- any updates to earnings per share and weighted average shares outstandingshares_outstanding- any updates to shares outstanding dataadjusted_numbers- any updates to adjusted metrics datafilings- any updates to filing information datasegments_and_kpis- any updates to company segments and key performance indicators data
Statement Types
When applicable, the statement_type field will contain:
income_statement- Income statement databalance_sheet- Balance sheet datacash_flow_statement- Cash flow statement data
Reliability and Retries
Automatic Retries
Our webhook system includes automatic retry logic:
- Maximum Retries: 2 additional attempts after the initial request
- Retry Delay: 5 seconds between retry attempts
- Timeout: 20 seconds per request
- Total Window: Up to 30 seconds for all attempts
Error Handling
We consider a webhook delivery successful when your endpoint returns HTTP status codes 200-299. Other status codes or timeouts will trigger retries.
Best Practices
- Idempotency: Handle duplicate webhook deliveries gracefully using the
timestampandcompany_keyfields - Fast Response: Acknowledge webhooks quickly and process data asynchronously if needed
- Error Logging: Log webhook failures for debugging
- Validation: Verify webhook payloads match the expected format
Verifying Webhook Signatures
To ensure webhook requests are genuinely from Fiscal and haven't been tampered with, we sign all webhooks using HMAC-SHA256. Your signing secret is available in your organization dashboard.
How Signature Verification Works
Each webhook request includes two headers, (the header names referring to our update engine that we've named Atlas):
X-Atlas-Signature: The HMAC-SHA256 signature in the formatsha256={hex_signature}X-Atlas-Timestamp: Unix timestamp (seconds) when the webhook was sent
The signature is computed using:
- Your signing secret (hex-encoded)
- A message string in the format:
{timestamp}.{json_payload} - The JSON payload is serialized with sorted keys and no spaces:
{"company_key":"NASDAQ_AAPL","event_type":"financial_data_updated","file_type":"financial_data_as_reported",...}
Implementation Examples
import hmac
import hashlib
import json
import time
def verify_webhook_signature(
payload_body: bytes,
signature_header: str,
timestamp_header: str,
signing_secret: str,
tolerance_seconds: int = 300
) -> bool:
"""
Verify the webhook signature from Fiscal.
Args:
payload_body: Raw request body as bytes
signature_header: Value of X-Atlas-Signature header
timestamp_header: Value of X-Atlas-Timestamp header
signing_secret: Your signing secret from the dashboard (hex string)
tolerance_seconds: Maximum age of webhook (default 5 minutes)
Returns:
True if signature is valid, False otherwise
"""
# Check timestamp to prevent replay attacks
try:
webhook_timestamp = int(timestamp_header)
current_timestamp = int(time.time())
if abs(current_timestamp - webhook_timestamp) > tolerance_seconds:
return False # Webhook is too old or from the future
except (ValueError, TypeError):
return False
# Parse the signature header (format: "sha256=<hex>")
if not signature_header.startswith("sha256="):
return False
expected_signature = signature_header[7:] # Remove "sha256=" prefix
# Reconstruct the signed message
# Important: Use the raw body bytes to preserve exact JSON formatting
signed_message = f"{timestamp_header}.{payload_body.decode('utf-8')}"
# Compute HMAC-SHA256 signature
secret_bytes = bytes.fromhex(signing_secret)
computed_signature = hmac.new(
key=secret_bytes,
msg=signed_message.encode('utf-8'),
digestmod=hashlib.sha256
).hexdigest()
# Constant-time comparison to prevent timing attacks
return hmac.compare_digest(computed_signature, expected_signature)
# Example usage in a Flask endpoint
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/webhooks/fiscal', methods=['POST'])
def handle_fiscal_webhook():
signature = request.headers.get('X-Atlas-Signature')
timestamp = request.headers.get('X-Atlas-Timestamp')
# Your signing secret from the Fiscal dashboard
signing_secret = 'your_signing_secret_hex_string_here'
if not verify_webhook_signature(
payload_body=request.get_data(),
signature_header=signature,
timestamp_header=timestamp,
signing_secret=signing_secret
):
return jsonify({'error': 'Invalid signature'}), 401
# Process the webhook payload
payload = request.get_json()
event_type = payload.get('event_type')
company_key = payload.get('company_key')
file_type = payload.get('file_type')
# Your business logic here
if event_type == 'financial_data_updated':
print(f"Financial data updated for {company_key}: {file_type}")
elif event_type == 'financial_data_deleted':
print(f"Financial data deleted for {company_key}: {file_type}")
elif event_type == 'company_updated':
print(f"Company profile updated for {company_key}")
elif event_type == 'company_deleted':
print(f"Company removed: {company_key}")
return jsonify({'status': 'success'}), 200
Important Security Notes
Signature Verification Best Practices:
Always verify signatures: Check the signature on every webhook request before processing
Use raw request body: Verify the signature against the raw request body before parsing JSON
Check timestamp: Reject webhooks older than 5 minutes to prevent replay attacks
Use constant-time comparison: Prevent timing attacks when comparing signatures
Keep secrets secure: Store signing secrets in environment variables or secret managers
Rotate secrets regularly: Update your signing secret periodically from the dashboard
Testing Signature Verification
You can test your signature verification implementation by:
- Creating a test webhook endpoint with logging enabled
- Waiting for webhook events to be delivered to your endpoint
- Logging the signature components to verify your implementation matches
If signature verification fails, check:
- The raw request body is used (not parsed JSON)
- The signing secret is correct and properly hex-decoded
- The timestamp header is included in the signed message
- The JSON payload format matches exactly (sorted keys, no spaces)
Security Considerations
Security Best Practices:
HTTPS Only: Always use HTTPS endpoints for webhook URLs
Signature Verification: Always verify webhook signatures before processing
Validation: Validate incoming payloads match expected format
Rate Limiting: Implement rate limiting on your webhook endpoints
Monitoring: Monitor for unusual webhook activity