Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Webhooks

A webhook is the REST API in reverse: instead of an external program asking the shop, the shop itself sends an HTTP request to a given address when something happens. An order is created — the warehouse knows a second later, without constant polling.

Creating in the admin panel

Open WooCommerce → Settings → Advanced → Webhooks and press Add webhook:

FieldWhat to enter
NameAnything, for yourself: Orders to the warehouse
StatusActive
TopicThe event: Order created
Delivery URLThe receiver's address: https://erp.example.com/woo-webhook.php
SecretA string for signing requests

After saving, every new order is sent to that address with a POST request.

Topics

TopicWhen it is sent
order.createdAn order is created
order.updatedAn order is changed, including its status
product.updatedA product is changed
customer.createdA customer is registered
coupon.createdA coupon is created

Every entity has the created, updated and deleted events.

What arrives

The request body is JSON of the same structure as a REST API response: for the order.created topic it is the whole order. Details of the event are in the headers:

HeaderWhat it holds
X-WC-Webhook-TopicThe topic: order.created
X-WC-Webhook-SignatureThe signature of the request body
X-WC-Webhook-Delivery-IDThe delivery number
X-WC-Webhook-SourceThe shop address

The receiver

A webhook is received by another program. Here is a minimal PHP receiver — the file lives on the accounting system's server, not in the plugin:

// erp.example.com/woo-webhook.php
$secret = 'your-webhook-secret';
$body = file_get_contents('php://input');
 
$expected = base64_encode(hash_hmac('sha256', $body, $secret, true));
$actual = $_SERVER['HTTP_X_WC_WEBHOOK_SIGNATURE'] ?? '';
 
if (!hash_equals($expected, $actual)) {
    http_response_code(401);
    exit;
}
 
$order = json_decode($body, true);
 
file_put_contents(__DIR__ . '/orders.log', $order['id'] . ' ' . $order['total'] . PHP_EOL, FILE_APPEND);
 
http_response_code(200);

The signature is an HMAC-SHA256 of the request body with the secret, encoded in Base64. The receiver computes it itself and compares it with the header: a match proves that your shop sent the request. Place an order — a line with the number and the total appears in orders.log.

Creating in code

A webhook is a WC_Webhook object. The file scripts/create-webhook.php:

// wp-content/plugins/acme-shop/scripts/create-webhook.php
$webhook = new WC_Webhook();
 
$webhook->set_name('Orders to the warehouse');
$webhook->set_user_id(1);
$webhook->set_topic('order.created');
$webhook->set_delivery_url('https://erp.example.com/woo-webhook.php');
$webhook->set_secret('your-webhook-secret');
$webhook->set_status('active');
 
$webhook->save();

Run the file with wp eval-file — the webhook appears in the list in the admin panel. The user from set_user_id() defines the permissions with which the data to send is collected.

How delivery works

The request is sent not at the moment of checkout but slightly later, by a background job — the customer does not wait for someone else's server. The receiver must answer with a code in the 200–299 range. After five failed deliveries in a row WooCommerce disables the webhook, and it is enabled again in the admin panel.

The delivery log is in WooCommerce → Status → Logs, the webhooks-delivery source.

Webhook or REST API

TaskWhat to choose
Learn about an event at onceA webhook
Get data on requestThe REST API
Change data in the shopThe REST API
Reconcile everything once a dayThe REST API with modified_after

They are usually combined: a webhook reports the event, and once a day the integration reconciles through the REST API in case deliveries were lost.

Common mistake

The receiver does not verify the signature and processes whatever arrives at the address. A webhook address becomes known sooner or later, and anyone can send a forged paid order. The hash_equals() check with the secret is mandatory — and the signature must be computed from the raw request body, not from JSON that was parsed and assembled again.

Thanks for reading!

Donate to the site — we work hard for you.