Conformance
Conformance
Why this page exists
A round-trip test — sign, then verify with the same code — passes even when the canonical string is wrong, because both halves are wrong in the same way. It proves self-consistency, not conformance.
The test that catches a misread specification is one where the signature came from somebody else's implementation. Every bundled scheme now has one.
Verified against a published vector
The provider publishes a worked example with an expected signature, and the suite asserts that exact string.
| Scheme | Source |
|---|---|
github |
The worked example in GitHub's webhook documentation |
slack |
The worked example in Slack's request-signing documentation |
standard-webhooks |
The test vector published with the specification |
twilio |
The worked example in Twilio's security documentation |
Plus the primitive itself: Support\Hmac is checked against RFC 4231 HMAC-SHA256 test
cases 1, 2 and 4 — the one thing whose failure would break every driver at once.
See tests/Feature/PublishedVectorsTest.php.
Verified against a foreign implementation
No published vector exists for these, so the signature was generated by the provider's own
SDK, captured, and pinned into the suite. The values live in
tests/Feature/CrossImplementationVectorsTest.php; the suite needs no network and no
vendor dependency to run them.
| Scheme | Signature produced by |
|---|---|
stripe |
stripe/stripe-php v21.1.1 — Stripe\WebhookSignature |
shopify |
shopify/shopify-api v6.1.1 — Webhooks\Registry::validateProcessHmac |
mailgun |
mailgun/mailgun-php — Api\Webhook::verifyWebhookSignature |
standard-webhooks |
standard-webhooks/standard-webhooks v1.0.2, the reference library |
postal |
openssl dgst -sha256 -sign and -sha1 -sign |
cbox |
The signer already deployed in the Cbox billing service, written independently of this package |
Each was also checked in the reverse direction at capture time — the foreign implementation accepting a signature this package produced — and the Shopify check was confirmed to reject a forgery, so the vendor's validator was demonstrably running rather than passing everything. For Stripe the two implementations produced byte-identical headers.
The generic HMAC driver
hmac has no provider and therefore no foreign implementation to check against. It is
covered by round-trip tests across digests and encodings, by tampering tests, and by
validation tests asserting that a definition it cannot honour exactly is refused rather
than silently defaulted.
That is the honest limit of what a configurable driver can be tested to. Its correctness for your provider depends on the definition you write, which is why an unhonourable definition throws at boot instead of guessing.
What "verified" does and does not mean
It means: given the same inputs, this implementation produces the byte string that the other implementation produced or accepts.
It does not mean the package has had a third-party security audit, holds any certification, or has been assessed against any standards framework. None of those have happened, and none are claimed anywhere in this repository.
Note also what a vector cannot cover: it proves the wire format, not the surrounding policy. Window enforcement, secret rotation, downgrade refusal and single-use enforcement are covered by their own behavioural tests, because no captured signature can express them.
The cryptography itself
Nothing here is hand-rolled.
Symmetric schemes compute every MAC with PHP's hash_hmac() and compare with
hash_equals(), both in Support\Hmac — so the constant-time comparison and the
no-early-exit loop over configured secrets are properties of one file rather than of nine.
The asymmetric scheme (postal) parses keys with openssl_pkey_get_public() and verifies
with openssl_verify(), in Support\Rsa. No padding, ASN.1 or digest handling is
implemented here. Constant-time comparison does not apply on that path and is not claimed:
the configured value is a public key, so a timing difference reveals nothing an attacker
cannot compute for themselves.
Algorithms are pinned per scheme and never read off the wire. GitHub's legacy SHA-1 header is refused rather than accepted alongside SHA-256, and Postal's SHA-256 header — when present — is the only one checked, so a corrupted strong signature cannot be downgraded to the weak one.