User Docs Developer Docs | |

CSRF Exemptions

Pubvana applies CSRF token verification globally to all POST, PUT, DELETE, and PATCH requests. This breaks webhook endpoints and external API callbacks that POST to your plugin without a CSRF token.

getCsrfExemptions()

Declare exempt routes by returning their URI patterns from getCsrfExemptions() in Plugin.php:

public function getCsrfExemptions(): array
{
    return [
        'dstore/webhooks/stripe',
        'dstore/webhooks/paypal',
        'dstore/webhooks/*',
    ];
}

Patterns are injected into CI4's CSRF filter except list during PluginManager::boot(). Matched against the request URI without the leading slash.

Pattern Syntax

PatternMatches
myplugin/webhooks/stripeExact path only
myplugin/webhooks/*Any single segment after webhooks/
myplugin/api/*Any path starting with myplugin/api/

Use the most specific pattern possible. Never exempt myplugin/* — that would expose all your routes to CSRF-free POSTs.

When to Use CSRF Exemptions

Use for: payment gateway webhooks, third-party service callbacks, mobile API endpoints using token auth.

Do NOT use for: admin forms, public comment forms, or any form submitted by your own JavaScript (include the CSRF token in the AJAX request instead).

Including CSRF in AJAX Requests

const csrfName  = document.querySelector('meta[name="csrf-token-name"]').content;
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;

fetch('/myplugin/admin/items', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        [csrfName]: csrfToken,
    },
    body: JSON.stringify({ title: 'New Item' }),
});

Webhook Security

Exempting a route from CSRF does not mean it is unprotected. Always verify webhook authenticity using the provider's signing secret:

$payload   = $this->request->getBody();
$sigHeader = $this->request->getHeaderLine('Stripe-Signature');
$secret    = setting('DStore.stripeWebhookSecret');

try {
    $event = StripeWebhook::constructEvent($payload, $sigHeader, $secret);
} catch (StripeExceptionSignatureVerificationException $e) {
    return $this->response->setStatusCode(400)->setJSON(['error' => 'Invalid signature']);
}