UTB Product Builder can push order data to an external endpoint automatically when a WooCommerce order changes status. Webhooks are configured per product and are independent of the REST API.
When webhooks fire
OrderWebhookSender registers listeners for all WooCommerce order status transitions:
| WooCommerce hook | Event key |
|---|
woocommerce_order_status_completed | order_completed |
woocommerce_order_status_processing | order_processing |
woocommerce_order_status_pending | order_pending |
woocommerce_order_status_refunded | order_refunded |
woocommerce_order_status_cancelled | order_cancelled |
woocommerce_order_status_failed | order_failed |
Each product can be configured to fire on one or more of these events. The enviar_automatico flag and a non-empty webhook_url must both be set for the webhook to be dispatched.
Webhook configuration
Webhooks are configured per product in the WordPress admin under UTB Builder → Webhooks / Order Metadata. The configuration is stored as JSON in the _utb_order_metadata post meta key.
Relevant fields in the configuration object:
| Field | Type | Description |
|---|
webhook_url | string | Destination URL for the POST request |
enviar_automatico | bool | Master on/off toggle |
webhook_events | string[] | List of event keys that trigger delivery (e.g. ["order_completed"]) |
custom_json_enabled | bool | Use a custom JSON template instead of the full payload |
custom_json_template | string | Template string with {variable} placeholders |
webhook_auth_type | string | bearer, basic, or oauth2 |
webhook_auth_token | string | Token or user:password string for auth |
webhook_oauth_url | string | OAuth2 token endpoint URL |
webhook_oauth_scope | string | OAuth2 scope |
webhook_oauth_credentials | string | Base64-encoded or client_id:client_secret credentials |
Payload
By default, the full OrderDataBuilder payload is sent as the request body. See the Orders API for the complete schema.
If Custom JSON is enabled, the plugin evaluates the template and replaces the following variables before sending:
| Variable | Value |
|---|
{order_id} | WooCommerce order ID |
{order_key} | WooCommerce order key |
{order_status} | Order status slug |
{order_total} | Order grand total |
{order_currency} | ISO 4217 currency code |
{date_iso} | ISO 8601 timestamp (UTC) |
{date_mysql} | MySQL-format timestamp (local time) |
{customer_id} | WordPress user ID |
{customer_email} | Billing email address |
{customer_name} | Full billing name |
{product_id} | WooCommerce product ID |
{product_name} | Product display name |
{product_sku} | Product SKU |
{flow_id} | UTB flow ID |
String variables (like {date_iso}) must be wrapped in double quotes inside your JSON template: "fecha": "{date_iso}". Numeric fields do not need quotes.
Every webhook delivery includes these headers:
| Header | Value |
|---|
Content-Type | application/json |
X-UTB-Webhook-Signature | HMAC-SHA256 signature (see below) |
X-UTB-Webhook-ID | WooCommerce order ID |
X-UTB-Webhook-Attempt | Delivery attempt number (1–3) |
User-Agent | UTB-Product-Builder/2.4.0 WordPress/{version} |
If authentication is configured (webhook_auth_type), an Authorization header is also included.
Signature verification
The X-UTB-Webhook-Signature header contains an HMAC-SHA256 hex digest of the JSON-encoded payload:
// Plugin generates the signature:
$secret = defined('UTB_WEBHOOK_SECRET') ? UTB_WEBHOOK_SECRET : wp_salt('auth');
$signature = hash_hmac('sha256', json_encode($payload_data), $secret);
The secret key defaults to WordPress’s auth salt. You can override it in wp-config.php:
define('UTB_WEBHOOK_SECRET', 'your-webhook-secret-here');
Verifying on the receiving end
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_UTB_WEBHOOK_SIGNATURE'] ?? '';
$secret = 'your-webhook-secret-here';
$expected = hash_hmac('sha256', $payload, $secret);
if (!hash_equals($expected, $signature)) {
http_response_code(403);
exit('Invalid signature');
}
$data = json_decode($payload, true);
// Process $data ...
Always use a constant-time comparison (hash_equals in PHP, hmac.compare_digest in Python) to prevent timing attacks.
Retry logic
If the destination URL returns a non-2xx status code or a connection error, the plugin schedules up to 2 additional attempts using wp_schedule_single_event:
| Attempt | Delay |
|---|
| 1 | Immediate |
| 2 | 5 seconds after failure |
| 3 | 15 seconds after attempt 2 fails |
Retries are only scheduled for server-side errors (5xx) and connection timeouts. Client errors (4xx) are logged but not retried.
WordPress cron must be running for retries to be processed. If DISABLE_WP_CRON is true, configure a real cron job to hit /wp-cron.php.
Webhook log table
All deliveries and debug events are written to wp_utb_webhook_logs:
| Column | Type | Description |
|---|
order_id | int | WooCommerce order ID |
webhook_url | varchar | Destination URL |
status | varchar | success, failed, or debug |
attempt | int | Attempt number |
response_code | int | HTTP status code received |
response_body | text | First 500 characters of the response body |
error_message | text | Error description, if any |
created_at | datetime | Timestamp of the attempt |
You can read logs programmatically:
$sender = new \UTB\ProductBuilder\API\OrderWebhookSender();
$logs = $sender->get_webhook_logs(1042); // order ID
Manual delivery
You can trigger a webhook delivery from code without waiting for an order status change:
$sender = new \UTB\ProductBuilder\API\OrderWebhookSender();
// Use the URL configured in product metadata:
$result = $sender->send_manual(1042);
// Or specify a URL explicitly:
$result = $sender->send_manual(1042, 'https://example.com/hook');
// $result['success'] is true/false
// $result['error'] contains the error message on failure