Add incoming webhook parsing and verification#6
Merged
Conversation
loevgaard
commented
Jun 23, 2026
Adds a standalone WebhookParser that verifies and parses the webhooks
Shipmondo POSTs to a consumer's server. Previously the SDK could only
manage webhook subscriptions via the API.
A delivery is `{"data": "<JWT>"}` (HS256-signed with the webhook key) plus
five SMD-* metadata headers. WebhookParser verifies the signature — the
only proof the request came from Shipmondo — and returns a typed
WebhookEvent exposing the header metadata and the decoded resource data.
- WebhookParser::parse(ServerRequestInterface, key) for PSR-7, and
parsePayload(body, headers, key) for non-PSR-7 frameworks.
- Verification pins HS256, rejecting alg:none / algorithm-confusion tokens.
- New exception branch off ShipmondoException: WebhookException ->
WebhookVerificationException (bad signature -> 401/403) and
MalformedWebhookException (bad JSON / non-HS256 token -> 400).
- Uses firebase/php-jwt ^7.0 (v7, not v6, to avoid CVE-2025-45769). v7
enforces a >=32-byte HS256 key, so the same floor is enforced when
creating webhooks (WebhookRequest::$key) and when verifying them.
- README "Receiving webhooks" section and CLAUDE.md updated.
5964fdf to
71f69fd
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds first-class support for receiving Shipmondo webhooks. Until now the SDK could only manage webhook subscriptions (
$client->webhooks()->create()/delete()/getPage()); there was nothing to help a consumer verify and parse the webhooks Shipmondo POSTs to their server.A delivery is
{"data": "<JWT>"}— the JWT HS256-signed with the webhookkey— plus fiveSMD-*metadata headers. Verifying that signature is the only proof a request genuinely came from Shipmondo.New surface (
Setono\Shipmondo\Webhook\)WebhookParserimplementsWebhookParserInterface, so consumers can type-hint the interface and inject the parser (or a mock) via DI. (The SDK has no endpoint interfaces by design, but the parser isn't an endpoint — it's a standalone, injectable service.)WebhookEventis a plain VO carrying the fiveSMD-*headers plus the verified JWT payload (webhookName,url,data). No$rawproperty — the envelope's three claims are all already typed properties, so aResource-style$rawwould be redundant;datais itself the untyped escape hatch for the resource.Client— it uses the per-webhook key, not API credentials), stateless, and takes thekeyper call so a multi-webhook server can pick the key bySMD-Webhook-Id.data(payload shapes can drift from GET responses).Security
alg:noneand algorithm-confusion tokens are rejected.ShipmondoExceptionmarker (no HTTP response, so it does not extendResponseAwareException):WebhookException→WebhookVerificationException(bad signature / wrong key → respond 401/403) andMalformedWebhookException(bad JSON / missingdata/ non-HS256 token → 400).Dependency:
firebase/php-jwt: ^7.0Pinned to v7, not v6: v6 carries advisory CVE-2025-45769 ("short HMAC key allowed"). The trade-off is that v7 enforces a minimum HMAC key length on verification (HS256 ⇒ ≥32 bytes / 256 bits, per RFC 7518 §3.2). So the SDK now enforces that same floor:
WebhookRequest::$keyis validated at construction (the one validatedPayloadfield — a deliberate, documented exception to the "no construction-timeAssert" convention).WebhookParserre-checks at verify time, so a webhook created outside the SDK with a short key fails with a clear "key must be ≥32 bytes" error rather than a confusing decode failure.WebhooksEndpointTestfixture (key: 'secret') was updated accordingly.Docs
CLAUDE.mdupdated: dependency rationale, theWebhookRequest.keyvalidation exception, and the webhook-parsing architecture.Verification
composer validate --strict+normalize: clean;--prefer-lowestresolvesfirebase/php-jwtv7.0.0 on PHP 8.1composer audit: no advisories