Skip to content

Webhooks Reference

middag-account receives inbound webhooks from external services and dispatches domain events internally. This document covers webhook endpoints, payload verification, and event types.

Inbound Webhook Endpoints

MethodEndpointAuthDescription
POST/webhooks/stripeStripe SignatureStripe payment events
POST/webhooks/hubspotHubSpot SignatureHubSpot CRM events

Base URL: /wp-json/middag-account/v1

No JWT authentication is used. Each webhook is validated by service-specific signatures.

Company Routing

Both endpoints use the X-Middag-Company header (middag_br or middag_global) to route events to the correct legal entity. A single unified endpoint per service handles both entities.

Stripe Webhook Events

Signature Verification

Stripe webhooks are validated using Stripe\Webhook::constructEvent() with the Stripe-Signature header and the webhook endpoint secret. Requests with an invalid or missing signature receive HTTP 401.

php
$event = \Stripe\Webhook::constructEvent(
    $payload,
    $request->getHeader('Stripe-Signature'),
    $webhookSecret
);

Processed Events

Stripe EventAction
checkout.session.completedMark order as paid, trigger entitlement flow
invoice.paidRecord invoice payment, update subscription
invoice.payment_failedFlag payment failure, notify organization
customer.subscription.updatedSync subscription status changes
customer.subscription.deletedCancel subscription, suspend entitlements
charge.refundedProcess refund, update order status

Payload Schema (checkout.session.completed)

json
{
    "id": "evt_1abc...",
    "type": "checkout.session.completed",
    "data": {
        "object": {
            "id": "cs_live_...",
            "customer": "cus_...",
            "payment_status": "paid",
            "metadata": {
                "organization_id": "42",
                "quote_id": "101"
            }
        }
    }
}

HubSpot Webhook Events

Signature Verification

HubSpot webhooks are validated using the X-HubSpot-Signature header with the HubSpot client secret. The signature is an HMAC-SHA256 hash of the client secret + request body.

php
$expectedHash = hash('sha256', $clientSecret . $requestBody);
if (!hash_equals($expectedHash, $signatureHeader)) {
    return new WP_REST_Response(['error' => 'Invalid signature'], 401);
}

Processed Events

HubSpot EventAction
Contact createdSync contact to WordPress user
Contact updatedUpdate user metadata
Deal stage changedUpdate linked quote status
Form submissionProcess lead capture, create organization

Error Handling

ScenarioHTTP ResponseBehavior
Missing signature header401Reject immediately
Invalid signature401Reject, log attempt
Unknown event type200Acknowledge but ignore
Processing failure500Log error, retry by provider

Webhook endpoints always return 200 for known events that are processed successfully. Returning non-2xx causes the provider to retry delivery.

Outgoing Webhook Events (Domain Events)

Domain events are dispatched internally via do_action(). External consumers listen via WordPress hooks. See Extension Points for the full list of domain events.

Key events that trigger cross-system side effects:

Domain EventSide Effects
middag/quote/paidCreate entitlement, update HubSpot deal
middag/entitlement/provisionedAuto-create license, environment, or service
middag/entitlement/suspendedNotify organization owner, pause downstream
middag/entitlement/cancelledRevoke downstream access

Security Requirements

  • Never process a webhook without validating the signature
  • Webhook secrets must be stored outside the webroot (env vars or wp-config)
  • All webhook processing is logged for audit purposes
  • Replay protection: Stripe provides timestamp-based replay protection; verify the tolerance window