DrPlugin

Webhooks

Updated October 24, 2026

Send signed notifications to your own systems when orders complete, licenses change or customer records are created, and verify each delivery.

How webhooks work

When an event happens in your store, TierMint sends an HTTP POST with a JSON description of it to each URL you registered for that event. A fulfillment system, a CRM, an accounting tool or your own backend can react right away, without polling the REST API.

Each endpoint has its own signing secret. TierMint signs every delivery with it, so the receiver can check that the request came from your store and was not changed.

The event catalog

Event Sent when
order.completed An order completes.
order.refunded A refund is recorded against an order, full or partial.
license.generated A license key is issued for a completed order.
license.activated A site is activated on a license, or a deactivated site is activated again.
license.deactivated A site's activation is released, by the customer's software, the customer's account or an admin.
license.expired A license passes its expiry date and its status changes to expired.
customer.created A new customer record is created.

Each endpoint receives only the events it subscribes to. With TierMint Pro active, the list also has license.renewed, sent when TierMint Pro extends a license's expiry, for example on a subscription renewal. The free plugin never extends a license, so it does not offer that event.

Add an endpoint

You need the manage_tiermint_settings capability, which administrators have.

  1. Open TierMint → Settings and click the Webhooks tab.
  2. Click Add endpoint.
  3. Enter the Endpoint URL. It has to be an http or https address with no user name or password in it.
  4. Under Events, tick the events the endpoint should receive. Pick at least one.
  5. Click Create endpoint.
  6. Click Copy secret and store the signing secret where your receiver can read it. Then click Done.

The new endpoint's signing secret, shown once, with a Copy secret button

TierMint sends deliveries through WordPress's safe HTTP request. It refuses addresses on your local or private network, and ports other than 80, 443 and 8080, so a receiver on localhost or a LAN address gets nothing.

Manage endpoints

The Webhooks tab lists each endpoint with its URL, how many events it subscribes to, an Active switch and its status.

The webhook endpoint table with URLs, event counts, the Active switch and a Failing badge on one endpoint

  • Turn off Active to pause an endpoint. TierMint sends nothing to it while it is off, and events that happen in that time are not sent later.
  • Failing marks an endpoint that TierMint switched off after every retry failed. Retries and failing endpoints explains when that happens.
  • To delete an endpoint, click Delete, then Confirm delete. Its stored secret is deleted too. Deliveries still queued for it are dropped.

The tab cannot change an endpoint's URL or events. Delete the endpoint and add a new one, or change it through the REST API.

Manage endpoints with the REST API

These routes need the manage_tiermint_settings capability. The licensing REST API page explains how to authenticate.

Route What it does
GET /tiermint/v1/settings/webhooks Returns catalog, the event names you can subscribe to, and endpoints, the registered endpoints without their secrets.
POST /tiermint/v1/settings/webhooks Creates an endpoint. Send url and events as a JSON body. The 201 response holds the endpoint record and the one-time secret.
PUT /tiermint/v1/settings/webhooks/{id} Changes url, events or active, sent as a JSON body. Setting active to true also clears the failing state.
DELETE /tiermint/v1/settings/webhooks/{id} Deletes an endpoint and its secret.

Each endpoint record has an id, url, events, active, failing and created_at. An unknown event name or a bad URL returns 400 tiermint_invalid_params.

Code that runs after TierMint has loaded can also create an endpoint through the WebhookEndpoints service, for example in a wp eval-file script during a deploy:

$endpoints = tiermint()->container()->get( \DrPlugin\WP\TierMint\Webhooks\WebhookEndpoints::class );

$result = $endpoints->create(
	'https://hooks.example.com/tiermint',
	array( 'order.completed', 'license.generated' )
);

// The secret is returned here once and never again.
echo $result['secret'];

Retries and failing endpoints

TierMint waits five seconds for a response. Any 2xx status counts as delivered. A timeout, a connection error or any other status is a failure, and TierMint tries again after a growing wait:

Retry Wait after the previous attempt
1 1 minute
2 15 minutes
3 1 hour
4 6 hours
5 24 hours

If the fifth retry also fails, TierMint switches the endpoint off and marks it Failing. A warning then shows on every wp-admin screen, to every user with the manage_tiermint_settings capability. It names the endpoint's URL and links to the Webhooks tab. You cannot dismiss it.

The admin notice naming a webhook endpoint that TierMint disabled after repeated delivery failures

Fix the receiver, then turn Active back on for the endpoint on the Webhooks tab. That clears the failing state and the warning, and the next event gets a fresh first attempt. Deliveries that failed are not sent again. Or delete the endpoint.

Where deliveries are logged

TierMint writes every attempt, delivered or failed, to its logs table ({prefix}tiermint_logs) with the type webhook_out. Each entry records the endpoint, URL, event, delivery id, attempt number, HTTP status and any connection error.

Deliveries run as queued tiermint_job_send_webhook actions in the tiermint group. You can see pending and finished attempts under Tools → Scheduled Actions. Finished attempts, delivered or not, stay there for 7 days. An action that ends in an error (status Failed) stays for three months.

Receive a delivery

This part is for whoever builds the receiving end.

The payload

Every delivery has the same JSON envelope:

{
  "id": "whk_a3c149d9-f2fc-4409-a0b3-46a1ac477671",
  "event": "order.completed",
  "created_at": "2026-10-13T14:32:05Z",
  "data": {}
}
Field Meaning
id The event's id: whk_ and a UUID. Every endpoint and every retry of the event gets the same id, so use it to skip events you already handled.
event The event name from the event catalog.
created_at When TierMint built the payload, in UTC, in ISO 8601.
data A snapshot of the order, license or customer.

TierMint builds the body once, when the event happens. A later change, such as a refund five minutes after order.completed, sends its own event and never rewrites a queued one. Every retry sends exactly the same bytes.

The data for each event

order.completed and order.refunded carry the order: id, uuid, status, type, customer_id, email, currency, subtotal, discount_total, tax_total, total, discount_code, gateway, created_at, completed_at and items. Each item has product_id, price_id, product_name, price_name, quantity, subtotal, discount, tax and total. order.refunded adds amount_refunded, the amount of this refund. Money is in the currency's smallest unit, such as cents. Payment gateway transaction ids are not included.

The license.* events carry the license: id, license_key, status, product_id, price_id, order_id, customer_id, activation_limit, activation_count, expires_at and created_at. license.generated adds order_uuid. license.activated and license.deactivated add site_url. The full license key is included, so an external system you control can deliver and sync licenses. Treat deliveries as secret.

customer.created carries id, user_id, email, name and created_at.

Dates inside data use the Y-m-d H:i:s format in UTC.

Headers

Header Value
Content-Type application/json
X-TierMint-Event The event name, such as order.completed.
X-TierMint-Delivery The delivery id: whd_ and a UUID. Each endpoint gets its own, and it stays the same across retries to that endpoint.
X-TierMint-Signature t={unix timestamp},v1={hex HMAC-SHA256}. TierMint signs each attempt again with a new timestamp.
User-Agent TierMint/{version}

Verify the signature

The signature is an HMAC-SHA256, keyed with the endpoint's signing secret, over the timestamp, a dot and the exact raw request body. In PHP:

<?php
// Read the raw body before anything parses it.
$raw_body = file_get_contents( 'php://input' );
$header   = $_SERVER['HTTP_X_TIERMINT_SIGNATURE'] ?? '';
$secret   = (string) getenv( 'TIERMINT_WEBHOOK_SECRET' ); // The secret shown when you created the endpoint.

function verify_tiermint_signature( string $header, string $raw_body, string $secret, int $tolerance = 300 ): bool {
	if ( ! preg_match( '/^t=(\d+),v1=([0-9a-f]+)$/', $header, $matches ) ) {
		return false;
	}

	$timestamp = (int) $matches[1];

	// Refuse old deliveries, so a captured request cannot be replayed later.
	if ( abs( time() - $timestamp ) > $tolerance ) {
		return false;
	}

	$expected = hash_hmac( 'sha256', $timestamp . '.' . $raw_body, $secret );

	// Compare in constant time.
	return hash_equals( $expected, $matches[2] );
}

if ( ! verify_tiermint_signature( $header, $raw_body, $secret ) ) {
	http_response_code( 400 );
	exit;
}

$event = json_decode( $raw_body, true );

// Queue the event for processing, then answer.
http_response_code( 200 );

A tolerance of 300 seconds works well. TierMint uses the same window when it checks incoming Stripe webhooks.

Respond quickly

Answer with a 2xx status as soon as you have checked the signature and stored or queued the event. Do the slow work afterwards. TierMint waits only five seconds, and a slow answer counts as a failure, so the same event arrives again.

The same event can arrive more than once, so make your processing safe to repeat, keyed on the payload's id.

Common questions

A delivery never arrived. Where do I look?

Check Tools → Scheduled Actions for a tiermint_job_send_webhook action, and the logs table for a webhook_out entry with the status your receiver returned. If the endpoint shows Failing, every retry failed: fix the receiver and turn the endpoint back on. Also check that the endpoint was active and subscribed to the event when it happened.

My signature check keeps failing. Why?

Most often the receiver checks a parsed or re-encoded body instead of the exact bytes TierMint sent. Read the raw body, such as php://input or your framework's raw body, before anything changes it. Also check that the receiver uses the secret of this endpoint.

Can one endpoint receive only some events?

Yes. Tick only those events when you add the endpoint, or send only those names in events through the REST API.

What happens to queued deliveries when I delete an endpoint?

They are dropped. A queued delivery that finds no endpoint, or a switched-off one, stops without sending.