Skip to content

fix: Register webhook ingress and add raw payload utilities - #8

Merged
nfebe merged 8 commits into
devfrom
fix/keep-the-raw-webhook-body
Oct 1, 2026
Merged

nfebe merged 8 commits into
devfrom
fix/keep-the-raw-webhook-body

Conversation

@nfebe

@nfebe nfebe commented Aug 22, 2026 •

Copy link
Copy Markdown
Contributor

Register incoming webhook URLs separately from authenticated management routes. Keep existing parsed payloads and their JSON storage unchanged. Add optional raw-body encoding utilities for applications that need exact bytes for signature checks. Add indexes for outgoing webhook selection by owner.

The model hands every incoming webhook a URL at /webhooks/ingress/{token}
and the package never routed it, so the address answered 404 unless the
application registered it by hand. It cannot sit behind the authenticated
stack either, because the sender is a third party holding nothing but the
token.

Each delivery stored only the parsed body, so the bytes a signature was
computed over were gone. Nothing could re-check that signature afterwards,
and anything the event was forwarded to would receive a re-encoding that
no longer matched. Senders that sign their payloads are the common case,
not the exception.

Dispatch also asks for one owner's active outgoing hooks on every event it
sends, and direction and is_active were indexed separately. Each holds two
values, so neither narrowed anything. With a thousand hooks on one owner,
twenty of them outgoing, it read 212 rows to find them. It now reads 21.

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. No specific code suggestions were generated. See the overview comment for a summary.

@github-actions

github-actions Bot commented Aug 22, 2026 •

Copy link
Copy Markdown

Coverage Report
PR coverage: 65.97%
Baseline: 56.51%
Change: ✅+9.5%

nfebe added 3 commits August 22, 2026 17:41
Keeping the parsed copy alongside the raw one was the same data twice. The
parsed form is derived on read instead, so there is one column and nothing
is duplicated.

It cannot go the other way round. Decoding and re-encoding does not
reproduce what was sent: spacing, unicode escaping, slashes, float
formatting and large integers all shift, and any of those breaks a
signature computed over the original.

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.

Comment thread src/Models/WebhookEvent.php Outdated
return $json;
}

// Some senders post a form rather than a document.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parse_str() yields a non-empty array for any non-empty string, so decoded() returns a bogus array (e.g. ['hello_world' => '']) for plain-text or XML bodies instead of null. Guard the fallback so only actual form bodies are parsed.

Suggested change
// Some senders post a form rather than a document.
// Some senders post a form rather than a document.
if (! str_contains($raw, '=')) {
return null;
}
parse_str($raw, $form);
return $form === [] ? null : $form;

@sourceant

sourceant Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Code Review Summary

Incoming webhook ingress is now registered as a separate route group under route_prefix, controlled by a new ingress_middleware config that defaults to empty and does not inherit route_middleware. A new RawPayload helper encodes exact request bytes into JSON envelopes for application signature checks. The webhooks table index is replaced with an owner/direction/active composite index plus a direction/active index.

🚀 Key Improvements

  • Ingress routes are unauthenticated by default, matching token-in-URL sender authentication.
  • RawPayload round-trips non-UTF-8 bodies via base64, keeping HMAC signatures intact.
  • New composite index webhooks_owner_dispatch supports outbound selection by owner.

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.

@nfebe nfebe changed the title fix: Serve the incoming address, and keep the bytes it arrives as fix: Register webhook ingress and add raw payload utilities Oct 1, 2026

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

src/Http/Controllers/WebhookController.php

$request->all() only holds form/JSON input, so senders posting text/plain, XML or binary bodies now store an empty payload while still getting a 202, and their exact bytes can no longer be verified. Keep a raw fallback (e.g. RawPayload::encode($request->getContent())) when parsed input is empty, or such deliveries are silently unrecorded.

src/Http/Controllers/WebhookController.php

$request->all() is empty for content types Laravel does not parse (text/plain, application/xml, application/octet-stream), so those events record an empty payload and the delivered body is discarded, leaving no bytes for signature verification. Fall back to storing the raw content via RawPayload::encode($request->getContent()) when the parsed input is empty.

src/Http/Controllers/WebhookController.php

The default ingress now stores $request->all(), which is empty for bodies that are neither JSON nor form-encoded (text/plain, text/xml, application/octet-stream, or a missing content type). Such deliveries still return 202 but are persisted with no body, so the sender's payload cannot be replayed or signature-checked. Preserve the raw content for these senders, or document that they are unsupported.

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.

@nfebe
nfebe merged commit 239f71e into dev Oct 1, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant