Skip to main content
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 hookEvent key
woocommerce_order_status_completedorder_completed
woocommerce_order_status_processingorder_processing
woocommerce_order_status_pendingorder_pending
woocommerce_order_status_refundedorder_refunded
woocommerce_order_status_cancelledorder_cancelled
woocommerce_order_status_failedorder_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:
FieldTypeDescription
webhook_urlstringDestination URL for the POST request
enviar_automaticoboolMaster on/off toggle
webhook_eventsstring[]List of event keys that trigger delivery (e.g. ["order_completed"])
custom_json_enabledboolUse a custom JSON template instead of the full payload
custom_json_templatestringTemplate string with {variable} placeholders
webhook_auth_typestringbearer, basic, or oauth2
webhook_auth_tokenstringToken or user:password string for auth
webhook_oauth_urlstringOAuth2 token endpoint URL
webhook_oauth_scopestringOAuth2 scope
webhook_oauth_credentialsstringBase64-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:
VariableValue
{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.

Request headers

Every webhook delivery includes these headers:
HeaderValue
Content-Typeapplication/json
X-UTB-Webhook-SignatureHMAC-SHA256 signature (see below)
X-UTB-Webhook-IDWooCommerce order ID
X-UTB-Webhook-AttemptDelivery attempt number (1–3)
User-AgentUTB-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:
AttemptDelay
1Immediate
25 seconds after failure
315 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:
ColumnTypeDescription
order_idintWooCommerce order ID
webhook_urlvarcharDestination URL
statusvarcharsuccess, failed, or debug
attemptintAttempt number
response_codeintHTTP status code received
response_bodytextFirst 500 characters of the response body
error_messagetextError description, if any
created_atdatetimeTimestamp 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

Build docs developers (and LLMs) love