Webhooks in Laravel
The SDK verifies the signature and gives you the event as an array. See Receiving webhooks for how delivery works and the event reference for all events.
The route
Exclude the route from CSRF protection in bootstrap/app.php:
php
->withMiddleware(function (Middleware $middleware) {
$middleware->validateCsrfTokens(except: ['webhooks/*']);
})Then verify each request with the raw body and answer 2xx within 10 seconds:
php
use Anikeen\Id\Exceptions\SignatureVerificationException;
use Anikeen\Id\Webhooks\EventType;
use Anikeen\Id\Webhooks\Webhook;
Route::post('webhooks/anikeen', function (Request $request) {
try {
$event = Webhook::constructEvent(
$request->getContent(),
$request->header('Anikeen-Signature'),
config('services.anikeen.webhook_secret'),
);
} catch (SignatureVerificationException) {
abort(400);
}
if (! ProcessedEvent::firstOrCreate(['id' => $event['id']])->wasRecentlyCreated) {
return response()->noContent(); // already handled
}
$object = $event['data']['object'];
match ($event['type']) {
EventType::ORDER_COMPLETED => Shop::fulfil($object['id'], $object['shipping_address']),
EventType::ORDER_CANCELLED => Shop::release($object['id']),
EventType::SUBSCRIPTION_ACTIVATED => Pass::grant($object['id']),
EventType::SUBSCRIPTION_CANCELLED => Pass::revoke($object['id']),
EventType::CHECKOUT_COMPLETED => Panel::provision($object['subscription_ids']),
default => null,
};
return response()->noContent();
});Webhook::constructEvent() rejects signatures older or newer than 5 minutes (the optional fourth argument changes the tolerance in seconds). Webhook::verify() runs the same checks (signature and timestamp) without decoding the event. EventType::all() lists every event type.
User and preference events
php
use Anikeen\Id\Resources\User;
$object = $event['data']['object'];
if ($event['type'] === EventType::USER_UPDATED) {
$profile = new User($object);
$account = Account::firstWhere('anikeen_id', $profile->id());
if ($account && $profile->version() > $account->profile_version) {
$account->update([
'name' => $profile->name(),
'email' => $profile->email() ?? $account->email, // null while unverified
'profile_version' => $profile->version(),
]);
}
}
if ($event['type'] === EventType::USER_DELETED) {
Account::firstWhere('anikeen_id', $object['id'])?->anonymise();
}
if ($event['type'] === EventType::PREFERENCE_UPDATED) {
Cache::forget("preferences:{$object['user_id']}");
}App access beta
php
if ($event['type'] === EventType::APP_ACCESS_REVOKED) {
// $object = {object: "app_access", user_id, client: {id, name}, revoked_at}
Delegation::release($object['user_id'], $object['client']['id']);
}