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:
- Secure API key storage in WordPress
- Creating and validating sessions with
wp_remote_post() - Receiving signed webhooks through the WordPress REST API
- 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.
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
| Issue | What to check |
|---|---|
| 401 from create/validate | Secret key is sk_..., header is Authorization: Bearer ... |
| Signature mismatch | Verify raw body is used and webhook secret matches dashboard value |
| User not marked verified | Confirm externalUserId is set during session creation and event is verification.completed |
| Redirect loops | Ensure return URL page handles sessionId and stores result before redirecting |
| WordPress REST endpoint not reachable | Confirm permalinks enabled and route path is correct (/wp-json/brand/v1/webhook) |