Independent Editorial DeskWordPress Releases, Builds, and Operations
Back to Archive
Hardening NotesImplementation Notes

WordPress Webhook Receiver Example: Secure Incoming Requests With HMAC

Build a secure WordPress REST webhook receiver with HMAC verification, timestamp checks, idempotency, safe JSON handling, and clean error responses.

Published

April 22, 2026

Reading Time

3 min read

Updated

April 22, 2026

Abstract secured webhook request flow with HMAC lock verification.
Control LedgerHardening Notes

Hardening Notes

Baselines, access reduction, and default settings that stand up in production.

Best For

Teams preparing, launching, or maintaining WordPress as a backend service in a production stack.

Primary Topics

Hardening NotesImplementation Notes

Editorial Focus

Control Ledger: Baselines, access reduction, and default settings that stand up in production. Updated on April 22, 2026.

Full Report

Last reviewed: April 22, 2026

Webhook receivers are common in WordPress integrations, but many are shipped as public endpoints with only a hidden URL as protection. A hidden URL is not authentication. If an external service can trigger changes inside WordPress, the request needs a real verification model.

This guide shows a webhook receiver built on the WordPress REST API with HMAC signature verification, JSON parsing, idempotency, and safe responses.

Integration scenario

Use this pattern when an external service sends events to WordPress: payment completed, scan finished, deployment complete, content approved, or ticket updated. The receiver should verify the sender before processing the event.

Register the webhook route

<?php
add_action( 'rest_api_init', 'vulnwp_register_webhook_route' );

function vulnwp_register_webhook_route() {
	register_rest_route(
		'vulnwp/v1',
		'/webhook',
		array(
			'methods'             => WP_REST_Server::CREATABLE,
			'callback'            => 'vulnwp_handle_webhook',
			'permission_callback' => 'vulnwp_verify_webhook_signature',
		)
	);
}

The route uses permission_callback for signature verification so invalid requests never reach the main handler.

Verify an HMAC signature

function vulnwp_verify_webhook_signature( WP_REST_Request $request ) {
	$secret = get_option( 'vulnwp_webhook_secret' );

	if ( ! is_string( $secret ) || '' === $secret ) {
		return new WP_Error( 'webhook_not_configured', 'Webhook secret is not configured.', array( 'status' => 500 ) );
	}

	$signature = $request->get_header( 'x-vulnwp-signature' );
	$body      = $request->get_body();
	$expected  = hash_hmac( 'sha256', $body, $secret );

	if ( ! is_string( $signature ) || ! hash_equals( $expected, $signature ) ) {
		return new WP_Error( 'webhook_forbidden', 'Invalid webhook signature.', array( 'status' => 401 ) );
	}

	return true;
}

Use hash_equals() for comparison. Do not use loose comparison for signatures.

Timestamp replay protection

Some providers include a timestamp header in the signed payload. If your provider supports that, reject old timestamps to reduce replay risk. The exact implementation depends on how the provider builds signatures, but the principle is the same: verify both integrity and freshness.

$timestamp = absint( $request->get_header( 'x-vulnwp-timestamp' ) );

if ( ! $timestamp || abs( time() - $timestamp ) > 5 * MINUTE_IN_SECONDS ) {
	return new WP_Error( 'webhook_expired', 'Webhook timestamp is outside the allowed window.', array( 'status' => 401 ) );
}

Do not invent a timestamp rule if the provider does not sign it. Unsigned timestamps can be changed by an attacker.

Handle the event

function vulnwp_handle_webhook( WP_REST_Request $request ) {
	$payload = json_decode( $request->get_body(), true );

	if ( JSON_ERROR_NONE !== json_last_error() || ! is_array( $payload ) ) {
		return new WP_Error( 'invalid_json', 'Invalid JSON payload.', array( 'status' => 400 ) );
	}

	$event_id = isset( $payload['id'] ) ? sanitize_text_field( $payload['id'] ) : '';
	$type     = isset( $payload['type'] ) ? sanitize_key( $payload['type'] ) : '';

	if ( '' === $event_id || '' === $type ) {
		return new WP_Error( 'missing_fields', 'Webhook event is missing required fields.', array( 'status' => 400 ) );
	}

	if ( get_option( 'vulnwp_webhook_event_' . $event_id ) ) {
		return rest_ensure_response( array( 'status' => 'duplicate_ignored' ) );
	}

	update_option( 'vulnwp_webhook_event_' . $event_id, time(), false );

	return rest_ensure_response(
		array(
			'status' => 'accepted',
			'type'   => $type,
		)
	);
}

Production checklist

  • Use HTTPS for webhook delivery.
  • Verify the raw request body before trusting JSON fields.
  • Use hash_hmac() and hash_equals().
  • Make processing idempotent with an event ID.
  • Return safe error messages without leaking secrets.
  • Rotate webhook secrets when exposure is suspected.

Common mistakes

  • Using a hidden URL as authentication. URLs leak in logs and browser histories.
  • Verifying decoded JSON instead of raw body. Signature algorithms usually sign the raw body.
  • No idempotency. Many providers retry webhooks.
  • Processing before verification. Invalid requests should stop at the permission callback.
  • Logging secrets. Never log the shared secret or full signed payload if it contains private data.

Related reading

This extends our register_rest_route example and pairs with the HTTP API example for outbound integrations.

References and further reading

Popular Guides

Popular WordPress guides to read next.

These articles connect recurring production concerns: implementation details, updates, troubleshooting, recovery paths, and operational cleanup.

Continue Reading

More from the archive.

Diagnostic dashboard scene representing a WordPress Site Health review before major updates.
01Build Pattern
Implementation Notes

Build Pattern

Extension points, code paths, and implementation choices that should survive contact with production.

May 21, 2026 · 3 min read

WordPress Site Health Check Before Major Updates: What to Review First

A pre-update WordPress Site Health checklist covering loopbacks, connectivity, debug settings, and environment readiness.

Structured data and route review scene representing permalink validation after a WordPress migration.
02Build Pattern
Implementation Notes

Build Pattern

Extension points, code paths, and implementation choices that should survive contact with production.

May 21, 2026 · 3 min read

WordPress Permalink Checklist After Migration: Catch URL Problems Early

A post-migration WordPress permalink checklist for checking rewrite rules, post URLs, archives, and redirect noise.

Technical media workspace representing image preparation and optimization before upload to WordPress.
03Build Pattern
Implementation Notes

Build Pattern

Extension points, code paths, and implementation choices that should survive contact with production.

May 21, 2026 · 3 min read

WordPress Image Optimization Checklist: What to Fix Before Upload

A practical WordPress image optimization checklist covering dimensions, compression, formats, and Media settings before upload.