Skip to main content

WordPress Integration

Use this guide when your site is built on WordPress and you want to run server-side session creation, validation, and webhooks in PHP.

Overview

This guide covers:

  1. Secure API key storage in WordPress
  2. Creating and validating sessions with wp_remote_post()
  3. Receiving signed webhooks through the WordPress REST API
  4. Building common integration patterns (shortcode, content restriction, WooCommerce gating)

Prerequisites

  • WordPress 5.0+
  • PHP 7.4+
  • Admin access to add theme/plugin code
  • PrivateAV secret key (sk_...)
  • HTTPS URL for your site and webhook endpoint

Store API Keys Securely

Recommended: define secrets in wp-config.php.

// wp-config.php
define('PRIVATEAV_SECRET_KEY', 'sk_your_secret_key');
define('WEBHOOK_SECRET', 'whsec_your_webhook_secret');

Read them in your integration code:

$secretKey = defined('PRIVATEAV_SECRET_KEY') ? constant('PRIVATEAV_SECRET_KEY') : '';
$webhookSecret = defined('WEBHOOK_SECRET') ? constant('WEBHOOK_SECRET') : '';

Alternative: store encrypted values in wp_options or a secrets plugin if your deployment requires admin-managed runtime configuration.

warning

Avoid hardcoding secrets in theme templates or exposing secrets in frontend JavaScript.

Create Session with wp_remote_post()

<?php
function brand_create_verification_session(array $args = []): array {
$secretKey = defined('PRIVATEAV_SECRET_KEY') ? constant('PRIVATEAV_SECRET_KEY') : '';

$payload = [
'returnUrl' => home_url('/verification-complete/'),
'externalUserId' => (string) get_current_user_id(),
'verificationMode' => $args['verificationMode'] ?? 'L1',
'challengeAge' => $args['challengeAge'] ?? 25,
];

$response = wp_remote_post('https://api.privateav.com/api/v1/sessions/create', [
'headers' => [
'Authorization' => 'Bearer ' . $secretKey,
'Content-Type' => 'application/json',
],
'body' => wp_json_encode($payload),
'timeout' => 20,
]);

if (is_wp_error($response)) {
return ['ok' => false, 'error' => $response->get_error_message()];
}

$status = wp_remote_retrieve_response_code($response);
$body = json_decode(wp_remote_retrieve_body($response), true);

if ($status < 200 || $status >= 300) {
return ['ok' => false, 'error' => $body['message'] ?? 'Session creation failed'];
}

return [
'ok' => true,
'sessionId' => $body['sessionId'] ?? null,
'verifyUrl' => $body['verifyUrl'] ?? null,
];
}

Validate Session with wp_remote_post()

<?php
function brand_validate_verification_session(string $sessionId): array {
$secretKey = defined('PRIVATEAV_SECRET_KEY') ? constant('PRIVATEAV_SECRET_KEY') : '';

$response = wp_remote_post('https://api.privateav.com/api/v1/sessions/validate', [
'headers' => [
'Authorization' => 'Bearer ' . $secretKey,
'Content-Type' => 'application/json',
],
'body' => wp_json_encode(['sessionId' => $sessionId]),
'timeout' => 20,
]);

if (is_wp_error($response)) {
return ['ok' => false, 'error' => $response->get_error_message()];
}

$status = wp_remote_retrieve_response_code($response);
$body = json_decode(wp_remote_retrieve_body($response), true);

if ($status < 200 || $status >= 300) {
return ['ok' => false, 'error' => $body['message'] ?? 'Session validation failed'];
}

return ['ok' => true, 'result' => $body];
}

Add a Webhook Endpoint (WordPress REST API)

Register a custom REST route in a plugin or your theme bootstrap:

<?php
add_action('rest_api_init', function () {
register_rest_route('brand/v1', '/webhook', [
'methods' => 'POST',
'callback' => 'brand_handle_webhook',
'permission_callback' => '__return_true',
]);
});

Verify signature and process events:

<?php
function brand_get_signature_from_headers(WP_REST_Request $request): ?string {
$headerName = 'x-privateav-signature';

// WordPress normalizes incoming header names; this is usually sufficient.
$signature = $request->get_header($headerName);
if (!empty($signature)) {
return $signature;
}

// Fallback for hosts that expose headers differently.
$serverKey = 'HTTP_' . strtoupper(str_replace('-', '_', $headerName));
return $_SERVER[$serverKey] ?? null;
}

function brand_handle_webhook(WP_REST_Request $request): WP_REST_Response {
$rawBody = $request->get_body();
$signature = brand_get_signature_from_headers($request);
$webhookSecret = defined('WEBHOOK_SECRET') ? constant('WEBHOOK_SECRET') : '';

if (!empty($webhookSecret) && !empty($signature)) {
$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $webhookSecret);
if (!hash_equals($expected, $signature)) {
return new WP_REST_Response(['error' => 'Invalid signature'], 401);
}
}

$payload = json_decode($rawBody, true);
$event = $payload['event'] ?? null;
$data = $payload['data'] ?? [];

if ($event === 'verification.completed' && !empty($data['externalUserId'])) {
update_user_meta((int) $data['externalUserId'], 'brand_age_verified', 1);
}

return new WP_REST_Response(['received' => true], 200);
}

Add a Shortcode for Verify Button

<?php
add_shortcode('brand_verify_button', function () {
if (!is_user_logged_in()) {
return '<p>Please sign in to verify your age.</p>';
}

$session = brand_create_verification_session();
if (empty($session['ok']) || empty($session['verifyUrl'])) {
return '<p>Unable to start verification. Please try again.</p>';
}

$url = esc_url($session['verifyUrl']);
return '<a class="button" href="' . $url . '">Verify your age</a>';
});

Use it in content:

[brand_verify_button]

Content Restriction Helper

<?php
function brand_user_is_verified(int $userId): bool {
return (bool) get_user_meta($userId, 'brand_age_verified', true);
}

function brand_require_verified_user(): void {
if (!is_user_logged_in()) {
auth_redirect();
}

if (!brand_user_is_verified(get_current_user_id())) {
wp_safe_redirect(home_url('/age-verification-required/'));
exit;
}
}

Call brand_require_verified_user() in templates, route handlers, or hooks where protected content should be blocked.

WooCommerce Checkout Gating (Brief Pattern)

Use a checkout validation hook to block orders when user verification is required but missing:

<?php
add_action('woocommerce_after_checkout_validation', function ($data, $errors) {
$userId = get_current_user_id();
if (!$userId || !brand_user_is_verified($userId)) {
$errors->add('brand_age_verification', 'Age verification is required before checkout.');
}
}, 10, 2);

Troubleshooting

IssueWhat to check
401 from create/validateSecret key is sk_..., header is Authorization: Bearer ...
Signature mismatchVerify raw body is used and webhook secret matches dashboard value
User not marked verifiedConfirm externalUserId is set during session creation and event is verification.completed
Redirect loopsEnsure return URL page handles sessionId and stores result before redirecting
WordPress REST endpoint not reachableConfirm permalinks enabled and route path is correct (/wp-json/brand/v1/webhook)