Grav Web Services — Webhooks, OAuth, SSO and LDAP Integration
In this tutorial, you'll learn Grav web services — integrating webhooks for real-time events, configuring OAuth providers for social login, setting up single sign-on (SSO), LDAP authentication, and connecting Grav with external APIs.
What You'll Learn
- Webhook integration: sending and receiving webhooks
- OAuth authentication with GitHub, Google, and Facebook
- Single sign-on (SSO) configuration
- LDAP authentication for enterprise environments
- External API integrations and service connections
- Event-driven automation with webhooks
Why It Matters
In WordPress, webhooks and integrations require plugins or custom code. In Grav, you build integrations directly in plugins using events and HTTP clients. Webhooks let Grav notify external services when events happen (page saved, user registered). OAuth and SSO let users log in with existing accounts. LDAP integrates with corporate directories. These integrations turn Grav from a standalone CMS into a connected platform.
Real-World Use
A developer documentation site needs to: notify a Slack channel when new documentation is published, allow users to log in with their GitHub accounts, sync user accounts with the company LDAP directory, and automatically deploy to production when pages are saved. All of these integrations are handled by a single plugin that subscribes to Grav events and makes API calls to external services.
Learning Path
flowchart LR
A["Grav API"] --> B["Web Services
← You are here"]:::current
B --> C["E-commerce with Grav"]
C --> D["Caching Deep Dive"]
D --> E["Performance Optimization"]
E --> F["Security"]
F --> G["Git Workflow"]
classDef current fill:#38bdf8,color:#0f172a,stroke-width:2px
Sending Webhooks
Send webhook notifications when Grav events occur:
user/plugins/webhooks/webhooks.php:
<?php
namespace Grav\Plugin;
use Grav\Common\Plugin;
class WebhooksPlugin extends Plugin
{
public static function getSubscribedEvents()
{
return [
'onPluginsInitialized' => ['onPluginsInitialized', 0],
];
}
public function onPluginsInitialized()
{
if ($this->isAdmin()) {
$this->enable([
'onAdminAfterSave' => ['onAdminAfterSave', 0],
]);
}
}
public function onAdminAfterSave($event)
{
$page = $event['page'];
$webhookUrl = $this->grav['config']->get('plugins.webhooks.page_update_url');
if (!$webhookUrl) {
return;
}
$data = [
'event' => 'page_saved',
'title' => $page->title(),
'route' => $page->route(),
'url' => $page->url(true),
'modified' => $page->modified(),
'timestamp' => time(),
];
$this->sendWebhook($webhookUrl, $data);
}
private function sendWebhook($url, $data)
{
$client = new \GuzzleHttp\Client();
try {
$client->post($url, [
'json' => $data,
'timeout' => 5,
'headers' => [
'User-Agent' => 'Grav-Webhook/1.0',
'X-Webhook-Event' => $data['event'],
],
]);
} catch (\Exception $e) {
$this->grav['log']->error('Webhook failed: ' . $e->getMessage());
}
}
}
Webhook Configuration
user/plugins/webhooks/webhooks.yaml:
enabled: true
page_update_url: 'https://hooks.slack.com/services/TXXXX/BXXXX/XXXXX'
deploy_webhook: 'https://api.netlify.com/build_hooks/XXXXX'
notify_on:
- page_save
- page_delete
- user_register
Deploy Webhook Example
public function onAdminAfterSave($event)
{
$deployUrl = $this->grav['config']->get('plugins.webhooks.deploy_webhook');
if ($deployUrl) {
$this->sendWebhook($deployUrl, [
'trigger' => 'content_update',
'page' => $event['page']->route(),
]);
}
}
Receiving Webhooks
Accept incoming webhooks from external services:
public function onPageInitialized()
{
$route = $this->grav['uri']->route();
if ($route === '/webhook/receive') {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
exit;
}
// Verify webhook secret
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$secret = $this->grav['config']->get('plugins.webhooks.secret');
$payload = file_get_contents('php://input');
$expected = hash_hmac('sha256', $payload, $secret);
if (!hash_equals($expected, $signature)) {
http_response_code(401);
echo json_encode(['error' => 'Invalid signature']);
exit;
}
// Process webhook
$data = json_decode($payload, true);
$this->processIncomingWebhook($data);
http_response_code(200);
echo json_encode(['status' => 'received']);
exit;
}
}
private function processIncomingWebhook($data)
{
$event = $data['event'] ?? 'unknown';
switch ($event) {
case 'github.push':
$this->handleGitHubPush($data);
break;
case 'stripe.payment_succeeded':
$this->handlePaymentSuccess($data);
break;
}
}
OAuth Authentication
Configure OAuth providers for social login:
user/config/plugins/login.yaml:
oauth:
enabled: true
providers:
github:
enabled: true
client_id: 'your-github-client-id'
client_secret: 'your-github-secret'
options:
scope: ['user:email']
google:
enabled: true
client_id: 'your-google-client-id'
client_secret: 'your-google-secret'
options:
scope: ['email', 'profile']
facebook:
enabled: true
client_id: 'your-facebook-app-id'
client_secret: 'your-facebook-app-secret'
Customizing OAuth Behavior
<?php
namespace Grav\Plugin;
class OAuthPlugin extends Plugin
{
public static function getSubscribedEvents()
{
return [
'onOAuthAfterLogin' => ['onOAuthAfterLogin', 0],
];
}
public function onOAuthAfterLogin($event)
{
$provider = $event['provider'];
$userData = $event['user'];
$gravUser = $event['grav_user'];
// Automatically create user if not exists
if (!$gravUser) {
$accounts = $this->grav['accounts'];
$gravUser = $accounts->add([
'username' => $userData['email'],
'email' => $userData['email'],
'fullname' => $userData['name'] ?? $userData['login'],
'access' => ['site' => ['login' => true]],
'state' => 'enabled',
]);
$accounts->save($gravUser);
}
}
}
Single Sign-On (SSO)
For SSO with SAML or OpenID Connect, use a plugin:
# user/config/plugins/sso.yaml
enabled: true
provider: saml
saml:
idp:
entity_id: 'https://idp.example.com/metadata'
sso_url: 'https://idp.example.com/sso'
x509_cert: 'path/to/cert.pem'
sp:
entity_id: 'https://grav.example.com/saml'
acs_url: 'https://grav.example.com/saml/acs'
LDAP Authentication
Configure LDAP in a custom plugin:
# user/config/plugins/ldap.yaml
enabled: true
server:
host: 'ldap.example.com'
port: 389
use_ssl: false
base_dn: 'dc=example,dc=com'
user_dn: 'uid=%s,ou=users,dc=example,dc=com'
filter: '(uid=%s)'
options:
auto_create: true
default_access:
site:
login: true
LDAP authentication handler:
<?php
namespace Grav\Plugin;
class LdapPlugin extends Plugin
{
public static function getSubscribedEvents()
{
return [
'onUserAuthenticate' => ['onUserAuthenticate', 0],
];
}
public function onUserAuthenticate($event)
{
$credentials = $event['credentials'];
$username = $credentials['username'];
$password = $credentials['password'];
// Try LDAP first, fall back to local auth
$ldapUser = $this->authenticateLdap($username, $password);
if ($ldapUser) {
$event->setUser($ldapUser);
$event->setStatus('success');
}
}
private function authenticateLdap($username, $password)
{
$config = $this->grav['config']->get('plugins.ldap');
$ldap = ldap_connect($config['server']['host'], $config['server']['port']);
ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
$userDn = str_replace('%s', ldap_escape($username, '', LDAP_ESCAPE_DN), $config['server']['user_dn']);
if (@ldap_bind($ldap, $userDn, $password)) {
// LDAP authentication successful
if ($config['options']['auto_create']) {
$this->autoCreateUser($username);
}
return $this->grav['accounts']->load($username);
}
return null;
}
}
Learning Path
flowchart LR
A["Grav API"] --> B["Web Services
← You are here"]:::current
B --> C["E-commerce with Grav"]
C --> D["Caching Deep Dive"]
D --> E["Performance Optimization"]
classDef current fill:#38bdf8,color:#0f172a,stroke-width:2px
Common Mistakes
Not verifying webhook signatures: Incoming webhooks should verify a signature or secret to ensure the request is from the expected service. Without verification, anyone can send fake webhooks.
Blocking on webhook delivery: Webhook HTTP calls should have a short timeout (5 seconds) and be wrapped in try-catch. A slow or failing webhook should not delay the page response.
Storing OAuth secrets in templates: OAuth client secrets should be in plugin configuration, not in templates or version-controlled files. Use
user/config/files added to.gitignore.Not handling OAuth token refresh: OAuth access tokens expire. Implement token refresh logic to avoid suddenly losing access to the provider's API.
LDAP connection pooling: Opening a new LDAP connection on every request is slow. Use persistent connections or Caching for LDAP authentication results.
Practice Questions
How do you send a webhook when a page is saved in Grav? Answer: Subscribe to
onAdminAfterSaveevent, extract page data, and use Guzzle HTTP client to POST the data to the webhook URL.How do you verify an incoming webhook is legitimate? Answer: Compare a hash-based message authentication code (HMAC) of the payload against a shared secret. The webhook sender includes the HMAC in a header.
What OAuth providers does Grav's Login plugin support? Answer: GitHub, Google, Facebook, and any provider that supports OAuth 2.0. Additional providers can be added through custom plugins.
How do you set up LDAP authentication as a fallback? Answer: Subscribe to
onUserAuthenticate, try LDAP first, and if it fails, let the default authentication handler Process the credentials as local authentication.Challenge: Build a complete integration plugin that connects Grav with three external services. Implement: a Slack webhook that notifies a channel whenever a new page is published (include page title, URL, and author), a GitHub OAuth login that creates Grav user accounts from GitHub profiles, an incoming webhook endpoint that accepts JSON payloads from a CI/CD service and triggers a cache clear, and an email notification integration that sends a digest of daily content changes. Include proper error handling, logging, configuration in YAML, and security verification for incoming webhooks.
FAQ
Mini Project
Goal: Build an integration hub plugin with multiple web services.
- Create a webhook sender that notifies Slack when pages are published
- Create an incoming webhook receiver for CI/CD deployment triggers
- Configure GitHub OAuth login with automatic account creation
- Create a Git webhook handler that auto-deploys on push
- Add a Stripe webhook handler for payment notifications
- Implement webhook signature verification for all incoming hooks
- Add a dashboard widget showing recent webhook activity
- Create a log viewer for webhook delivery status
- Add retry logic for failed webhook deliveries
- Test the entire integration system end-to-end
What's Next
Now you can integrate Grav with external services. Next, learn e-commerce:
Continue to Lesson 34: E-commerce with Grav — SimpleCart, payment gateways, and product management.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro