
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.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.Once configured, external services POST to:
What Is Entity Webhook?
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.
- 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
Flexible Field Extraction
Entity Webhook gives Drupal a complete webhook toolkit built around three capabilities: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.
Deduplication and Updates
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.
Verification Built In
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.
Developer Extensibility
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.
Broadcasting Outbound Webhooks
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.We’re excited to introduce Entity Webhook, now available as a contributed module on drupal.org.
Polling for APIs That Don’t Push
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.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.
How It Compares to Other Webhook Modules
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.
| 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 | ✓ | ✓ | ✓ | ✓ |
The key design decision: all three are driven by the admin UI. No custom module code is required to get a working integration.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.The core module handles inbound webhooks through a three-tier configuration structure: endpoints, source types, and field mappings.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.
Getting Started
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.
Let’s Build Something
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.




