Drupal module · core + payment + auth + notify + Google + Slack + AI domains

Connector Hub

One connection layer for Drupal. Configure a secret and an endpoint once, and let a small plugin handle the one thing that's actually different about each external system: a payment provider, a Google service, or whatever comes next. A separate, pluggable authentication layer means a connector picks how it authenticates (an API key, a bearer token, OAuth2) instead of reinventing it per provider.

Drupal ^10.3 || ^11.0 phpcs: Drupal + DrupalPractice phpstan level 6 GPL-2.0-or-later

Why a hub at all

Every integration tends to rebuild the same plumbing from scratch. Connector Hub builds that plumbing once, so a new integration is mostly the one part that's genuinely different.

× Without a shared core

Each integration invents its own place to store a secret, its own retry logic for a flaky API, its own webhook route with its own signature check, and its own admin screen to configure it. Five integrations, five slightly different versions of the same problem.

✓ With Connector Hub

A connector endpoint holds the connection: which plugin, which secret (via the Key module, never in configuration), whether it sends, receives, or both. A queue handles delivery and retries. A webhook route handles rate limiting and the path token. The plugin only has to speak the one dialect that system actually uses.

How it's layered

Connector Hub itself stays generic on purpose. Payment was the first domain built on it; authentication is the first primitive pulled back out into its own layer, once a second domain (Google) needed the exact OAuth2 flow a payment adapter's own secret handling never had to worry about.

connector_hub
Core: connections, plugins, queued delivery, authenticated webhooks.
shipped
connector_hub_auth
Pluggable authentication: API key, Basic Auth, Bearer token, OAuth2 (Client Credentials and Authorization Code + PKCE) — plus a separate, equally pluggable inbound webhook verification layer.
shipped
connector_hub_notify
The second primitive, generalized the same way auth was: how a connector tells a human something happened, not which channel does it. Added once a second domain (AI) needed exactly what Slack's own notification capability already proved out.
shipped
HMAC signing · next mTLS · next SAML2 · next

Four domains build on the same foundation above:

connector_hub_payment
Payment domain: exact amounts, per-operation status, idempotent registration.
shipped
connector_hub_payment_mollie
Mollie adapter: hosted checkout, status verified by re-fetch, refunds.
shipped
connector_hub_payment_stripe
Stripe adapter: Checkout Sessions, a genuinely signed webhook, refunds.
shipped
connector_hub_payment_multisafepay
MultiSafepay adapter: hosted redirect orders, a signed webhook, refunds.
shipped
connector_hub_payment_buckaroo
Buckaroo adapter: hosted transactions, a signed push, refunds.
shipped
connector_hub_payment_demo
Working example: create an order, pay through any configured adapter, react to its status.
shipped
Adyen · next CRM ERP PEPPOL
connector_hub_google
Google domain: the one place any Google URL or scope is hardcoded.
shipped
connector_hub_google_login
"Sign in with Google" registration and login, via externalauth.
shipped
connector_hub_google_calendar
Create and list events on a connected Google Calendar.
shipped
Gmail · next Sheets & Drive · next Analytics (GA4) · next
connector_hub_slack
Post notifications to a Slack channel, receive a slash command — the first domain neither Google nor payment was designed around.
shipped
Block Kit formatting · next Multi-channel fan-out · next OAuth install flow · next
connector_hub_ai
AI generation domain: one capability interface, a queued job (a real generation call can take tens of seconds), an unpublished draft node on completion — and the first module in this project with a direct dependency on connector_hub_notify.
shipped
connector_hub_ai_openai
Chat Completions, authenticated with the same bearer_token shape Mollie and Stripe already use.
shipped
connector_hub_ai_anthropic
Messages API — the first real use of connector_hub_auth's api_key provider (x-api-key, not Bearer) anywhere in this project.
shipped
connector_hub_ai_slack
Bridge module: a verified Slack slash command queues a generation job. The reverse direction — telling Slack a draft is ready — turned out not to need a bridge at all, see connector_hub_notify above.
shipped
Streaming responses · next A configurable system prompt · next

What's actually built

Nothing here is described as finished until it has run inside a real Drupal 11 install, not just read back correctly on the page.

connector_hub shipped

  • Connector endpoint entity: one per connection, multiple accounts per plugin supported
  • Secrets resolved through the Key module only, never stored in configuration
  • Queued outbound delivery with automatic retries and a give-up event
  • Generic webhook route; each plugin verifies its own signature scheme
  • Rate limiting and a secret path token on every inbound call
  • Admin dashboard: last sync and recent error count per connector endpoint
  • Webhooks verified, durably queued, deduplicated, and retried with a dead-letter event
  • phpcs and phpstan (level 6) clean, Kernel and Functional tests for every auth path

connector_hub_payment shipped

  • Money as integer minor units with an explicit decimal exponent, never a float
  • One record per operation (initiation, capture, cancel, refund), not one per purchase
  • Idempotency key backed by a real database unique constraint, not just a lookup
  • A status-changed event, so fulfillment reacts to a confirmed status, not a webhook alone
  • Read-only admin listing of every payment operation
  • Per-operation locking closes a real concurrent-update race
  • Cron-driven reconciliation, with duplicate-prevention and outage backoff
  • Separate success/cancel URLs, backward compatible

connector_hub_payment_mollie shipped

  • Hosted checkout via Mollie's Payments API (the deprecated Orders API is not used)
  • Status never trusted from the webhook alone: always re-fetched through an authenticated API call
  • Refunds, with the provider reference and resulting status attached automatically
  • No explicit authorize/capture step yet - see the roadmap

connector_hub_payment_stripe shipped

  • Hosted Checkout Sessions, with the amount built inline - no pre-created Stripe prices
  • A genuinely signed webhook, verified for real and trusted directly - no re-fetch needed
  • A second secret (the webhook signing secret) resolved entirely in the plugin's own settings
  • Refunds; no subscriptions/recurring billing yet - see the roadmap

connector_hub_payment_multisafepay shipped

  • Hosted redirect orders, called directly via the REST API - no SDK dependency
  • A genuinely signed webhook, keyed by the same secret already on the connection
  • Client-generated order IDs, known before the call is even made
  • Per-method customer field requirements still need confirming - see the roadmap

connector_hub_payment_buckaroo shipped

  • Hosted transactions, called directly via the REST API - no SDK dependency
  • Two unrelated signing schemes: a fixed one outbound, a configurable one for the push
  • Provider-generated transaction keys, refunds included
  • Generic refund shape and default payment-method list still need confirming - see the roadmap

connector_hub_payment_demo shipped

  • The first real (non-test) caller of register()/initiatePayment() in either project
  • Create an order, pick any eligible endpoint, start a payment, react to its confirmed status
  • Correctly ignores the generic dispatcher's outbound flag - no payment adapter uses it
  • Scoped to create/pay/react only - no refund demo yet, by design

connector_hub_auth shipped

  • Five outbound authentication providers: API key, Basic Auth, Bearer token, OAuth2 Client Credentials, OAuth2 Authorization Code + PKCE
  • A separate, equally pluggable inbound webhook verification layer: HMAC signature (configurable algorithm) and Bearer token
  • Composition, not inheritance: a connector implements one small marker interface and hands back a configured provider, so the same mechanism serves any domain
  • The generic admin "Connect"/"Reconnect" flow for any interactive provider, with zero changes to connector_hub core
  • Four existing payment adapters (Mollie, Stripe, MultiSafepay) already migrated onto it; Buckaroo's own two signing schemes stayed as-is, deliberately, since neither fits a generic shape

connector_hub_notify shipped

  • NotifierInterface: one capability a connector implements to send a notification, the same composition discipline connector_hub_auth already set
  • NotificationDispatcher never throws: an unconfigured or failing notification is logged and reported back as false, never propagated into an unrelated caller's own domain logic
  • Generalized only once a second real caller needed it (connector_hub_ai), the same bar connector_hub_auth itself was held to against payment and Google
  • connector_hub_slack is the first real implementation, delegating to its own postMessage()

connector_hub_google shipped

  • Thin Google-specific layer on top of connector_hub_auth: the one place Google's own OAuth URLs are hardcoded
  • CalendarEventManagerInterface, the capability interface a Google adapter implements to create/list events
  • No OAuth controller of its own — the generic connect flow in connector_hub_auth handles admin connect/reconnect for every Google adapter

connector_hub_google_login shipped

  • "Sign in with Google" for visitor registration and login, via the externalauth module
  • A placeable, cacheable, themeable Block for the sign-in link, not a fragile alter on a shared core form
  • Verifies the ID token's real signature against Google's own JWKS before trusting any identity claim

connector_hub_google_calendar shipped

  • Create and list events on a connected Google Calendar via the REST API directly, no SDK dependency
  • Requests the narrowest scope that does the job: calendar.events only, never full calendar management
  • Site-level, ongoing access: a stored refresh token, reused and renewed automatically as it expires

connector_hub_slack shipped

  • Post a notification to any Slack channel; outbound auth reuses connector_hub_auth's bearer_token, unchanged
  • Receive a slash command, verified against Slack's own timestamp-prefixed HMAC — a bespoke scheme, not the generic hmac_signature provider, documented as a deliberate finding
  • Replies via Slack's own response_url, matching its async pattern instead of fighting it
  • The first domain neither payment nor Google was designed around — proves the core/auth split generalizes past both
  • Also implements connector_hub_notify's generic NotifierInterface, delegating to its own postMessage()

connector_hub_ai shipped

  • TextGeneratorInterface: one capability a connector implements to generate text; a word target is advisory, folded into the prompt, never an enforced constraint
  • Generation runs as a queued job, not a direct call-and-wait — the first adapter in this project whose provider call can genuinely take tens of seconds
  • Creates an unpublished ai_draft node on completion; reviewing it is visiting its normal edit page like anything else on the site
  • Ships its own field_draft_body rather than node's shared body field — both node_add_body_field() and the field storage it depends on are deprecated with no replacement and on their way out in Drupal 12
  • Notifies through connector_hub_notify directly; never hears of Slack, email, or any other channel

connector_hub_ai_openai shipped

  • Chat Completions API, authenticated with connector_hub_auth's bearer_token — confirmed against OpenAI's own API reference, not assumed
  • A plugin-level model setting (default gpt-4o-mini); no SDK dependency, called directly via Guzzle

connector_hub_ai_anthropic shipped

  • Messages API via x-api-key, not Bearer — the first real consumer of connector_hub_auth's api_key provider anywhere in this project
  • Adds the mandatory anthropic-version header itself, as a plugin-level setting; not a secret, so not connector_hub_auth's concern

connector_hub_ai_slack shipped

  • Bridges Slack and AI in one direction only: a verified slash command queues a generation job
  • The reverse direction (notifying Slack) was removed from this module entirely once it turned out to generalize — see connector_hub_notify
  • A deliberately simple parse: the first standalone number in the command text is the word target, the rest is the prompt

Roadmap

In order, and open to reshuffling if a real project needs a specific provider sooner.

01

Adyen

With an explicit choice between its Sessions/Drop-in flow and the lower-level API, made once and documented, not assumed.

02

Gmail, Sheets & Drive, Analytics (GA4)

Three more Google adapters on the exact same connector_hub_auth OAuth2 primitive — no core or auth-layer changes anticipated, only new submodules.

03

HMAC signing, mTLS, SAML2

Three more authentication mechanisms on connector_hub_auth's own roadmap, for whichever provider needs them first.

∞

Beyond payments and Google

CRM, ERP and PEPPOL e-invoicing connectors, built the same way: a small plugin on top of the same core, authenticating through whichever connector_hub_auth provider actually fits.

Getting started: Mollie

The same four steps apply to any provider this ships an adapter for - only the account and dashboard change. Here's Mollie's version; Stripe's own walkthrough (two secrets instead of one) is in the developer reference.

01

Create a Mollie account

Free to sign up, test mode works immediately - no business verification needed until live payments. Sign up on mollie.com.

02

Get a test API key

In the Mollie dashboard: Developers › API keys (or API access tokens, depending on your dashboard version). Copy the key starting with test_. Mollie's own guide on API keys.

03

Store the key and add a connection

Create a Key (Configuration › Key) holding that value, then a Connector endpoint (Configuration › Web services › Connector Hub) using the Mollie plugin and that Key.

04

Start a payment

Register an operation, then call PaymentInitiatorInterface::initiatePayment() with it, the endpoint, and a return URL - redirect the customer to the checkout URL it gives back.

Documentation

The full developer reference - architecture, code samples, and the reasoning behind it - lives on its own page. Here's the short version plus each module's own README.

connector_hub/README.md

Covers the connector endpoint entity, the plugin interface (send(), verifyInboundRequest(), the configuration form hooks), the dispatcher service, and the webhook controller. Includes the exact steps to add a new connector plugin in a separate module.

Admin UI
Configuration › Web services › Connector Hub
Permission
administer connector endpoints
Repository
View project on drupal.org
connector_hub_payment/README.md

Covers Money, PaymentStatus, RemoteReferences, the PaymentOperation entity, and the four small contracts an adapter implements only where it applies: initiating, reading status, refunding, and supplying customer details. connector_hub_payment_demo is a small, working example built on all of it, worth reading before building a new adapter.

Admin UI
Content › Payment operations
Permission
view payment operations
Repository
View project on drupal.org
connector_hub_auth/README.md

Covers the two pluggable provider systems (AuthenticationProviderInterface for outbound, InboundVerificationProviderInterface for inbound), the five/two shipped mechanisms, the composition-over-inheritance rule a connector follows to use one, and the generic admin connect flow for interactive providers.

Admin UI
Configuration › Web services › Connector Hub (adds a Connect/Reconnect action per endpoint)
Permission
administer connector endpoints (reused, no new permission)
Repository
View project on drupal.org
connector_hub_google/README.md

Covers GoogleConnectorPluginBase (the one place any Google URL is hardcoded), CalendarEventManagerInterface, and how the two shipped submodules — login and Calendar — each compose a connector_hub_auth provider instead of extending anything Google-specific.

Admin UI
Same collection page, via connector_hub_auth's own connect flow
Permission
administer connector endpoints (reused, no new permission)
Repository
View project on drupal.org
connector_hub_slack/README.md

Covers SlackNotifierInterface::postMessage(), the bespoke timestamp-prefixed HMAC verifyInboundRequest() uses instead of the generic provider, and how a slash command's reply reaches Slack asynchronously through its own response_url rather than the original request.

Admin UI
Same collection page, via connector_hub_auth's own connect flow
Permission
administer connector endpoints (reused, no new permission)
Repository
Not yet published on drupal.org
connector_hub_notify/README.md

Covers NotifierInterface::notify() and NotificationDispatcher's own "never throws" contract — and why this generalized now rather than speculatively, the same bar connector_hub_auth was held to.

Admin UI
Configuration › Web services › Connector Hub Notify
Permission
administer connector endpoints (reused, no new permission)
Repository
Not yet published on drupal.org
connector_hub_ai/README.md

Covers TextGeneratorInterface, AiGenerationWorker's queue-based design, the ai_draft bundle and its own field_draft_body, and the direct dependency on connector_hub_notify.

Admin UI
Same collection page, via connector_hub_auth's own connect flow
Permission
administer connector endpoints (reused, no new permission)
Repository
Not yet published on drupal.org
connector_hub_ai_slack/README.md

Covers SlashCommandToGenerationSubscriber's word-target parse, and why this bridge module ended up smaller than it started — the notify half moved into connector_hub_ai once it turned out to generalize.

Admin UI
Configuration › Web services › Connector Hub AI Slack
Permission
administer connector endpoints (reused, no new permission)
Repository
Not yet published on drupal.org

Contributing

Both modules are open source under GPL-2.0-or-later. Every change follows the same bar, documented in each module's CONTRIBUTING.md. Each repository also has its own API_STABILITY.md: semantic versioning intent from the first tagged release, which interfaces are covered by it, and the house rule for evolving one without breaking callers.

What every change needs

  • English only: code, comments, commit messages, UI strings
  • Drupal coding standards and DrupalPractice, phpstan at level 6
  • Dependency injection throughout, no service calls from inside a class that could take them as constructor arguments
  • A Kernel or Functional test for new behavior, positive and negative cases for anything security- or money-related

Local setup

Each module is tested against a real Drupal 11 site via DDEV, not assumed to work from reading the code.

# inside either module's own directory
ddev config --project-type=drupal11 --docroot=web
ddev add-on get ddev/ddev-drupal-contrib
ddev start && ddev poser && ddev symlink-project
# then
ddev phpcs && ddev phpstan analyze . && ddev phpunit web/modules/custom