Webhooks are one of the most useful tools in a modern integration toolkit. Instead of your Drupal site repeatedly asking "anything new?" on a schedule, an external system taps your shoulder the moment something changes. The result is faster data, fewer redundant requests, and integrations that actually behave like real-time systems.
At Aten, we build a lot of integrations. A recent project made the need for a more complete webhook solution clear: a client needed a centralized hub that could aggregate order data from Shopify and multiple Drupal Commerce sites, and keep customer addresses synchronized across all of them. Data was flowing in multiple directions, from multiple sources, with different payload formats. The existing options in the Drupal ecosystem either required significant custom code or handled one direction well but not the other. So we built something.
We're excited to introduce Entity Webhook, now available as a contributed module on drupal.org.
What Is Entity Webhook?
Entity Webhook gives Drupal a complete webhook toolkit built around three capabilities:
- Inbound (core module): Receive JSON payloads from external services and automatically create or update Drupal entities
- Outbound (
entity_webhook_broadcastsubmodule): Notify external systems when Drupal entities are created, updated, or deleted - Polling (
entity_webhook_pollingsubmodule): Actively fetch data from external APIs on a schedule, as a fallback for systems that don't push webhooks
The key design decision: all three are driven by the admin UI. No custom module code is required to get a working integration.
Receiving Webhooks and Upserting Entities
The core module handles inbound webhooks through a three-tier configuration structure: endpoints, source types, and field mappings.
An endpoint defines the target entity type — a Commerce Order, a taxonomy term, a user, or any custom entity. A source type represents a particular payload format from a particular service (Shopify, Stripe, another Drupal site), along with its verification method. A field mapping connects a JSON value in the payload to a Drupal entity field.
Once configured, external services POST to:
/webhook/{endpoint_name}/{source_type}
The module responds immediately with a 200, queues the payload, and processes it asynchronously during cron — keeping your endpoint fast and your site stable under load.
Flexible Field Extraction
Field values are extracted from the JSON payload using JSONPath expressions. A mapping like $.order.billing_address.first_name can reach deep into a nested payload structure. You can also combine multiple expressions or use hardcoded static values.
Before a value is written to an entity field, you can optionally run it through a mutation plugin to transform it: convert an ISO date string to a Unix timestamp, map a source status to a Drupal equivalent (paid → completed), convert a cents integer to a decimal price, apply a regex substitution, and more.
Deduplication and Updates
To keep entities in sync rather than creating duplicates, you mark one or more field mappings as identifiers. Before saving, the module performs a lookup — if an entity with those field values already exists, it updates it instead of creating a new one. Multiple identifier fields form a composite key.
Verification Built In
Three verification plugins are included: HMAC-SHA256 signature validation (with configurable header name and encoding), API Key validation from a header or query parameter, and an IP/CIDR whitelist. All verification runs synchronously before any processing begins.
Developer Extensibility
When configuration isn't enough, the module dispatches Symfony events before and after entity saves. A PreSaveEvent subscriber can modify the entity or cancel the save entirely. A PostSaveEvent subscriber can trigger downstream workflows or notifications — without touching core module code.
Broadcasting Outbound Webhooks
The entity_webhook_broadcast submodule monitors entity CRUD events and delivers signed JSON payloads to external endpoints. You configure outbound field mappings to define the payload shape, set a shared secret for HMAC-SHA256 signing, and optionally define conditions that filter which events trigger a broadcast.
Delivery is queue-based and asynchronous. If a delivery fails, the module retries with exponential backoff up to a configurable number of attempts. Every attempt — successful or not — is written to an audit log, which makes debugging integrations considerably less painful.
Polling for APIs That Don't Push
Not every external service supports webhooks. The entity_webhook_polling submodule handles those cases. You configure a schedule using a standard cron expression (e.g., */15 * * * *), implement a polling provider plugin for your API, and it feeds results into the same entity upsert pipeline used by inbound webhooks.
To avoid unnecessary processing, each fetched record is hashed with SHA-256. Only records whose hash has changed since the last run are queued for processing.
How It Compares to Other Webhook Modules
There are a few other maintained, Drupal 11-compatible webhook modules worth knowing about. Here's how they differ, based on reviewing each module's source code.
| Feature | entity_webhook | webhooks | webhook_receiver | symfony_webhook_receiver |
|---|---|---|---|---|
| Inbound webhooks | ✓ | ✓ | ✓ | ✓ |
| Entity upsert — config-driven, no code | ✓ | — | — | — |
| Field mapping UI | ✓ | — | — | — |
| Outbound broadcasting | Submodule | ✓ | — | — |
| Polling | Submodule | — | — | — |
| HMAC verification | ✓ | ✓ | Token-in-URL only | Symfony-native |
| API Key / IP whitelist verification | ✓ | — | — | — |
| Admin UI | ✓ | ✓ | — | — |
| Custom code required to process inbound | — | ✓ | ✓ | ✓ |
| Drupal 11 compatible | ✓ | ✓ | ✓ | ✓ |
Webhooks is the most established option and works well for outbound use cases. When it receives a webhook, it fires a webhook.receive Drupal event — your code handles what happens next. That flexibility is useful, but it means every inbound integration requires a custom event subscriber and no entity upsert comes for free.
Webhook Receiver is a code-first plugin framework. Its README is straightforward about this: "This module does not contain any graphical user interface." You extend a plugin base class, implement validatePayload() and processPayload(), and wire everything together in code. It's a solid foundation for developers building bespoke integrations, but every integration is custom work from scratch.
Symfony Webhook Receiver brings Symfony's Webhook component into Drupal. If your team is comfortable with Symfony's ConsumerInterface and service container conventions, it's a clean approach. Like the others, it has no admin UI, and each integration requires custom service definitions and consumer classes.
Entity Webhook is designed for the scenario where you want integrations to live in configuration — deployable, exportable via Drupal's config management, and maintainable without a developer on call every time a payload format changes.
Getting Started
composer require drupal/entity_webhook drush en entity_webhook # Enable submodules as needed: drush en entity_webhook_broadcast entity_webhook_polling
Then navigate to Administration > Configuration > Services > Entity Webhook to create your first endpoint. Full documentation is on the project page.
Let's Build Something
If you're working on a Drupal integration — connecting an e-commerce platform, a CRM, a payment processor, or another Drupal site — we'd love to help. Get in touch with the Aten team.