To enable support to http/2 we need to add a module
sudo a2enmod http2
In the apache config, we need a directive to switch on the mod.
Insert the directive below after the <VirtualHost *:443> tag.
Protocols h2 http/1.1
To test webhooks supporting HTTP/2
cythonize -3 -a -i WebHooks.py
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~./home/axion/projects/axion/factory.web/WebHooks.c: In function ‘__pyx_pf_8WebHooks_8WebHooks_28Commit.constprop’:./home/axion/projects/axion/factory.web/WebHooks.c:19888:8: note: ‘__pyx_v_rdg’ was declared here.19888 | long __pyx_v_rdg;. | ^~~~~~~~~~~
Updated : 2025-10-02
The webhook system implements three layers of validation to catch
output errors before they reach the client. This ensures data quality,
type safety, and business rule compliance.
Output validation runs in RenderResult() just before the response is
returned, validating every webhook response regardless of the code
path that generated it.
Validation Layers:
Method: _validate_output_required()
Purpose: Ensures all required RETURN parameters are present and
not empty.
What it catches:
Example scenario:
# Webhook definition says "customer_id" is required
# But workflow/SQL only returned:
results = {
"customer_name": "John Doe",
# Missing: "customer_id"
}
# ❌ OLD: Client receives incomplete data
# ✅ NEW: Validation fails with error:
# "Required output parameter 'customer_id' is missing or empty"
When this helps:
Method: _validate_output_types()
Purpose: Validates data types match expected ValueType from
def_webhook_parameters.
Supported types:
INT - Integer values onlyFLOAT - Numeric values (int or float)JSON - Valid JSON stringsWhat it catches:
Example scenarios:
# Parameter "order_total" defined as ValueType='FLOAT'
results = {
"order_total": "not_a_number" # ❌ String, not float
}
# Error: "Expected FLOAT for 'order_total', got non-numeric"
# Parameter "metadata" defined as ValueType='JSON'
results = {
"metadata": "{invalid json" # ❌ Malformed JSON
}
# Error: "Expected valid JSON for 'metadata', got invalid format"
# Parameter "item_count" defined as ValueType='INT'
results = {
"item_count": 3.14 # ❌ Float, not integer
}
# Error: "Expected INT for 'item_count', got decimal value"
When this helps:
Method: _validate_output()
Purpose: Runs custom business logic validations defined in
def_webhook_output_validations table.
What it catches:
Example custom validations:
-- Validation: "Total must match line items"
SELECT
CASE
WHEN {order_total} != (
SELECT SUM(price)
FROM line_items
WHERE order_id = {order_id}
)
THEN 'Order total does not match sum of line items'
ELSE ''
END as validation_result
-- Validation: "Customer must exist"
SELECT
CASE
WHEN NOT EXISTS (
SELECT 1
FROM customers
WHERE id = {customer_id}
)
THEN 'Customer ID does not exist in database'
ELSE ''
END as validation_result
-- Validation: "Credit limit not exceeded"
SELECT
CASE
WHEN {order_total} > (
SELECT credit_limit
FROM customers
WHERE id = {customer_id}
)
THEN 'Order exceeds customer credit limit'
ELSE ''
END as validation_result
When this helps:
CREATE TABLE `def_webhook_output_validations` (
`WebhookCode` varchar(255) NOT NULL,
`Package` varchar(100) NOT NULL DEFAULT '',
`Description` varchar(255) NOT NULL,
`Block` varchar(255) NOT NULL DEFAULT '',
`Rank` int(11) NOT NULL DEFAULT 1,
`ValidationSql` mediumtext DEFAULT NULL,
`ValidationNote` varchar(255) DEFAULT NULL,
`RemoteConnection` varchar(255) DEFAULT '',
`Active` char(1) DEFAULT 'Y',
PRIMARY KEY (`WebhookCode`,`Package`,`Description`,`Block`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO def_webhook_output_validations VALUES
('CREDIT_CHECK', 'homechoice', 'Score in valid range', '', 1,
'SELECT CASE WHEN {credit_score} NOT BETWEEN 0 AND 850
THEN "Credit score must be between 0 and 850" ELSE "" END',
'Invalid credit score range', '', 'Y'),
('CREDIT_CHECK', 'homechoice', 'Limit not negative', '', 2,
'SELECT CASE WHEN {credit_limit} < 0
THEN "Credit limit cannot be negative" ELSE "" END',
'Invalid credit limit', '', 'Y');
Scenario: Webhook returns customer credit check result
The Issue:
# Workflow had a bug - returned credit score as string
results = {
"customer_id": 12345,
"credit_score": "750", # ❌ Should be integer
"approved": "true", # ❌ Should be boolean
"credit_limit": None # ❌ Required field is None
}
How Each Layer Catches It:
Required Validation:
credit_limit is None but marked as Required='Y'Type Validation:
credit_score is "750" (string) but defined as INTCustom SQL Validation:
Result:
RenderResult()
├── Build results dictionary
├── Get package and archetype
│
├── _validate_output_required()
│ ├── Query: webhooks_get_required_output_params
│ ├── Check each required parameter exists
│ ├── Check values are not empty/None
│ └── Return False if validation fails
│
├── _validate_output_types()
│ ├── Get parameters from self.returns
│ ├── For each INT parameter:
│ │ └── Verify value is integer (not float)
│ ├── For each FLOAT parameter:
│ │ └── Verify value is numeric
│ └── For each JSON parameter:
│ └── Verify valid JSON format
│
├── _validate_output(package, archetype)
│ ├── Query: get_output_validations
│ ├── For each validation rule:
│ │ ├── Format SQL with output parameters
│ │ ├── Execute validation SQL
│ │ └── Check for error message
│ └── Return False if any validation fails
│
└── If all validations pass:
├── Persist results to bloom table
└── Return results to client
Before (No Output Validation):
After (Three-Layer Validation):
When validation fails:
{
"result": "Error",
"result_note": "Validation failed: <specific error message>",
"Statuscode": 400
}
All validation queries are defined in
factory.core/ObjHook.yaml:
queries:
# Output Validation
get_output_validations: |
SELECT ValidationSql, ValidationNote,
remote_connection, Description
FROM def_webhook_output_validations
WHERE webhookcode = '{webhook_code}'
AND package IN ('{package}', '{archetype}')
AND COALESCE(Active, 'Y') = 'Y'
ORDER BY Rank DESC
webhooks_get_required_output_params: |
SELECT Parameter, Required
FROM Def_Webhook_parameters
WHERE posttype = 'RETURN'
AND webhookcode = '{webhook_code}'
AND Required = 'Y'
AND coalesce(Package,'core')
IN ('{package}','core','adhoc')
ORDER BY Rank
To test output validation:
Example test case:
def test_output_validation():
hook = WebHooks()
hook.Read("TEST_WEBHOOK")
# Intentionally set wrong type
hook.results = {
"customer_id": "not_a_number", # Should be INT
"amount": None # Required but missing
}
result, status = hook.RenderResult()
assert status == 400
assert "validation failed" in result["result_note"].lower()
WEBHOOK_OUTPUT_VALIDATION_IMPLEMENTATION.md - ImplementationWEBHOOK_OUTPUT_VALIDATION_QUICKSTART.md - Quick referenceresource.notes/webhook_output_validation.md - ComprehensiveSQL_TO_YAML_PROGRESS.md - SQL migration progressUpdated: 2026-02-19