Skip to content
Resource type

Article

Version

1.0

Updated

KYC Webhook Signature Verification: Raw Body and Key Rotation

Preserve raw request bytes, verify the configured signature scheme and make key retirement explicit. Includes an eight-case synthetic R-73 receiving-policy trace.

Synthetic R-73 receiving-policy comparison: the same old-demo signature is eligible under P7 and rejected under P8 after retirement. This is a local fixture, not a provider test.

KYC webhook signature verification should authenticate the provider's exact signed input before an event can influence application state. Preserve the original request bytes, select the verifier from an approved configuration and record which eligible key version matched. A successful comparison establishes a limited authentication result. It does not approve an applicant or authorize every transition described in the payload.

The difficult part often appears during a change. Middleware starts parsing the body earlier, a signing secret is replaced or a retry arrives after the receiver has retired an old key. The endpoint still receives plausible JSON. What matters is whether the receiving system can explain why that particular delivery was accepted under the policy in force at its acceptance boundary.

This guide develops that decision with a fictional R-73 receiving-policy exercise and the published KYC Webhook Verification Record. The exercise uses public toy keys and synthetic data. It is a local calculation, not an executed provider delivery or a production security assessment.

Start with the provider's signing contract

Identify the exact webhook configuration before choosing an implementation. Record the environment and endpoint, then locate the provider's current description of the signed material. A protocol might authenticate only the body or combine it with delivery metadata. Adding a timestamp to your own calculation cannot improve compatibility when the publisher did not sign that representation.

The current Sumsub Webhook manager documentation describes x-payload-digest and x-payload-digest-alg, with an HMAC over the unaltered body bytes. It lists HMAC_SHA256_HEX as the new-webhook default, SHA512 as another option and SHA1 as legacy. Confirm the saved configuration rather than applying that default to an older endpoint.

Create a short adapter specification containing the expected algorithm, header syntax, digest encoding and source document. Include the access date and a real revision when one exists.

An access date is evidence of when you checked a page, not a version number issued by the provider. Retain the actual configuration revision separately.

Separate evidence from assumptions. A support answer confirming retry behavior belongs with the integration record, including its scope. A diagram copied from another provider is not evidence that your provider signs the same fields. If the signing description is incomplete, assign that question before treating a successful development example as a production contract.

Capture the body before transformations obscure it

Locate the first component that exposes the request body to application code. Trace the path through the gateway and any middleware to the verification function. Name where the original bytes are captured, whether they can be read again and which component may transform them.

A variable named rawBody is not proof of its origin.

The OWASP Webhook Security Cheat Sheet gives the relevant implementation rule:

Use the publisher's maintained verification library when available and follow its exact signing format.

For a protocol that signs body bytes, keep those bytes available until verification finishes. JSON parsing may preserve the values while losing the representation that was signed. RFC 8259 permits insignificant whitespace around structural characters. Removing that whitespace can therefore preserve valid JSON while changing the input to an HMAC calculation.

Ask for an ingress-level demonstration using the deployed middleware order. Record the captured byte length and a suitable restricted evidence reference. Do not log a real identity-verification payload merely to demonstrate capture. A synthetic request can expose a transformation without publishing applicant data. The local fixture below checks the calculation after bytes have already been supplied; it does not establish your gateway's behavior.

Make algorithm and digest handling explicit

Treat the incoming algorithm header as a value to validate against expected configuration. It should not give the sender unlimited choice among every algorithm installed in a runtime. If the adapter expects SHA256, a missing or unsupported scheme needs a defined rejection outcome. A permissive fallback can hide a configuration mismatch that should have remained visible.

Specify digest parsing with the same care. Hexadecimal and base64 are different representations. A digest with a prefix, unexpected length or malformed characters should not reach a comparison through an accidental coercion. Define how your HTTP stack handles repeated headers and header-name normalization. Test the representation the real adapter receives rather than guessing from a screenshot of an outbound request.

Use the maintained verifier where it fits the documented protocol. When implementing the comparison, use a cryptographic comparison function on appropriate, consistently typed values. Python's hmac documentation explains compare_digest and its input constraints.

Choosing that function does not prove that the whole endpoint has constant response time or that earlier parsing cannot disclose information.

Keep internal result categories distinct from the external response body. An engineering record can distinguish unsupported scheme from digest mismatch without exposing that detail to an unauthenticated caller. No category should silently mean that verification was skipped. If a dependency is unavailable, describe the result as incomplete and apply the approved failure policy.

Bind key selection to the receiving endpoint

An eligible key belongs to a particular integration context. Record the environment and the registered receiver before loading verification material. A test endpoint and a production endpoint should not become interchangeable simply because their payloads share a schema. The key inventory should explain which configuration authorizes each receiver to accept a delivery.

Keep outbound API credentials separate from inbound webhook signing material. They may have different owners, distribution paths and revocation procedures. A successful authenticated request to a provider API does not prove that the receiving endpoint has the correct webhook secret. Use distinct references in the deployment record so a team cannot mistakenly rotate one credential while believing it replaced the other.

Also define how the adapter selects the integration context when several tenants share infrastructure. An unverified applicant identifier or arbitrary request header should not grant access to an unrestricted secret lookup. Choose a bounded, reviewed routing rule appropriate to the actual protocol. Treat incoming routing values as untrusted until their role in the authentication design has been established.

If a receiver intentionally tests more than one eligible key, keep that set small and controlled by trusted configuration. Record the matching internal version without treating its label as provider-supplied identity. Where two entries accidentally contain the same secret, the first match cannot establish which label the publisher intended. Secret inventory checks and authentication checks answer different questions.

Assign access to signing material according to operational need. The person who can change a dashboard setting may not be the person who approves a production retirement. Document that handoff so an emergency responder can identify the authorized owner. This article proposes the inventory and routing review; the local fixture does not test tenant isolation, access controls or a secrets manager.

Inspect the local R-73 receiving-policy exercise

R-73 uses one artificial JSON object with a fixture reference and a reviewed-state label. Neither value identifies a real applicant. Two public toy key values are labeled old-demo and new-demo. The fictional receiving policy P7 makes both eligible. P8 makes only new-demo eligible.

These are explicit local assumptions, not a claim that Sumsub provides a particular rotation workflow.

The exercise signs the original bytes using each toy key. It then checks eight cases against their expected outcomes. The body is also parsed and compactly serialized to produce an intentionally different byte sequence with the same JSON value. No key material or raw provider payload is needed in the public result record.

  • R73-01 accepts the original body signed with old-demo under P7; R73-02 rejects the changed bytes against that original signature.
  • R73-03 accepts new-demo under P7; R73-04 rejects an old-demo signature when P8 no longer makes that key eligible.
  • R73-05 accepts new-demo under P8; R73-06 records a missing signature as a rejection.
  • R73-07 rejects malformed hexadecimal data; R73-08 rejects a scheme outside the fixture's configured SHA256 format.

All eight expected outcomes matched in the local run. The result demonstrates the configured calculation and key-eligibility decisions for those fixtures. It does not test a network receiver, provider retries, secret distribution or concurrent workers. A production adapter needs evidence for those boundaries separately. The narrower V-01/V-02 examples in the paired template remain useful for reviewing byte preservation alone.

R-73 separates original-byte capture, signature verification with eligible keys and later application checks. P7 allows old-demo and new-demo; P8 allows only new-demo. No HTTP or concurrent test is claimed.
Synthetic R-73 receiving-policy boundaries. Only the local sequential calculation was executed.

Separate planned rotation from a compromised secret

For a planned change, establish what both sides actually support. Ask whether the provider sends multiple signatures, switches one signing secret immediately or offers another documented transition mechanism. Confirm what happens to queued deliveries and retries. Do not infer those answers from the ability to edit a secret field in a dashboard.

The Standard Webhooks specification is a useful contrast: its signature-header format explicitly accommodates multiple signatures during rotation. That property belongs to that protocol. It does not establish that a differently named digest header follows the same syntax or that a particular KYC provider implements the specification.

Receiver-side overlap also needs an explicit boundary. Name the eligible key versions, the reason each remains eligible and the condition that removes the old version. Avoid a permanent collection of historical secrets tried against every request.

A successful match against an obsolete secret is not the same as acceptance under the current integration policy.

Suspected compromise changes the decision. Retaining a compromised secret for convenience permits further forged deliveries under that secret. Use the incident owner's revocation procedure instead of extending an ordinary migration window. Coordinate recovery of legitimate deliveries through a verified route. The existence of a retry backlog does not make a compromised credential safe to keep accepting.

Define retirement at the acceptance boundary

A receiver can verify a delivery while one policy revision is active and process it after a different revision has taken effect. Decide which point makes the authentication result usable for a durable action. Merely removing an old secret from one process's memory does not establish what every worker will permit during the transition.

For the fictional R-73 design, retain the policy revision with the verification result and carry it to the component that accepts work. If the intended rule forbids old-demo after P8 becomes authoritative, that component needs an enforceable check against the current eligibility decision. A record showing a P7 match explains an earlier calculation; it does not answer whether a later action is permitted.

Choose the coordination mechanism for the actual architecture. A transactional acceptance check, a versioned configuration barrier or another reviewed design may fit different systems. Specify what occurs if the authoritative policy cannot be read. State the accepted consistency assumptions and the unresolved cases rather than describing an ordinary cache refresh as immediate fleet-wide revocation.

The R-73 fixture evaluates P7 and P8 sequentially. It contains no concurrent acceptance test. Before deployment, exercise a delayed worker crossing the retirement boundary and record the actual result. Include workers that were paused or disconnected when the configuration changed. A healthy current instance cannot establish the behavior of a process still holding earlier state.

Fill the paired record for a rejected retired key

Use the Verification Record template to retain a compact decision, with implementation detail in the linked restricted evidence. For R73-04, the following filled record describes the local exercise. Keep the original nine field names so another reviewer can compare it with the blank form without translating a new schema.

  • delivery_ref: R73-04, synthetic local case.
  • provider_doc_version: fictional R-73 fixture contract, revision 1; provider comparison sources checked October 30, 2026.
  • body_capture_method: original synthetic bytes supplied directly to the fixture, with no HTTP ingress claim.
  • signature_scheme: configured HMAC_SHA256_HEX over body bytes, hexadecimal digest.
  • key_identifier_only: old-demo, a non-secret internal label that is ineligible under receiving policy P8.
  • verification_result: FAIL_SIGNATURE under P8; the current eligible set contains only new-demo.
  • rejection_route: local rejection result, with no application-state write in this fixture.
  • redacted_evidence_ref: R-73 local trace, case R73-04; no applicant payload or production secret.
  • owner: integration verification owner, a fictional role to assign in a real project.

This record does not claim the digest was never valid. It records that the current eligible set did not authenticate the supplied delivery. Preserve that distinction when an incident reviewer compares earlier and later policy revisions. Do not change a rejection into a pass merely because somebody can reproduce a match with a key that the policy has retired.

Record failure without copying sensitive inputs

Keep enough evidence to explain the decision without turning routine logs into an identity-document archive. Useful fields include an internal delivery reference, adapter build and policy revision, a sanitized result category and the responsible owner. Store any necessary raw evidence in a restricted system with appropriate retention rather than pasting it into tickets or chat.

Treat signature values and key references according to the actual threat model. A non-secret version label can help locate approved configuration. The secret itself has no place in the record. A body hash is also not an automatic anonymization control: consider whether the underlying values could be guessed or linked across records before retaining or exposing it.

Define external response behavior against the current provider policy. Sumsub's documented HTTP delivery table treats 401, 403 and 404 as permanent failures. Consequently, a response chosen for an authentication failure can affect redelivery. Verify that consequence when designing the rejection route; do not copy a generic status-code example while assuming the provider will keep retrying.

Distinguish rejection evidence from absence of evidence. A missing record might indicate a capture problem or an unavailable logging path. It does not prove that the endpoint rejected the event. Test the chosen observable outcome and establish how the team investigates a request that reached the ingress but never produced a verification record.

Keep authenticity separate from freshness and business ordering

A signature can remain valid when the same signed message is delivered again. It can also authenticate an event that the application has already acted on. Deduplication and permitted applicant-state transitions therefore require independent decisions after authentication. A successful digest comparison supplies no universal rule for changing a completed review or granting account access.

Use a signed timestamp or event identifier for a security decision only when the selected protocol authenticates that field. An additional unsigned header does not inherit trust from an HMAC over the body. Retain a clear map of which inputs are authenticated, which are transport observations and which are values established by your own application.

This guide does not prescribe a provider migration, a replay restart position or a general event-ordering policy. Those are separate engineering records. Its narrow release question is whether the adapter can demonstrate authentication under the intended key policy before a different component decides what the event may change.

Roll back code without restoring retired authority

A deployment rollback can restore an earlier verifier build while the security policy must remain newer. Consider a receiver running P8 because old-demo was deliberately retired. Returning to an earlier application image should not silently restore P7 and make that old key eligible again. Include the key-policy dependency in the rollback plan instead of treating configuration as an incidental file bundled with the code.

Separate the versions that a release record needs. The adapter build identifies executable behavior. The key-policy revision identifies eligible authentication material. The source document identifies the contract being implemented. Keeping those references distinct lets a reviewer explain why an earlier build is or is not compatible with the current retirement decision.

A single release name conceals that distinction.

Before an ordinary rollback, check whether the target build understands the current policy representation and failure outcomes. If it cannot enforce the active retirement rule, identify another recovery path or hold the affected capability under an approved procedure. Do not restore an obsolete secret merely to make a previous deployment start successfully. The application owner needs to see that compatibility problem explicitly.

Configuration distribution needs its own observation. A control-plane update may be saved while a worker still uses its last accepted snapshot. Record how the system detects that disagreement and what the worker is permitted to do until it converges. A time-to-live value is a design parameter, not evidence that every instance adopted a retirement within that interval.

For the R-73 review, add an unexecuted acceptance exercise: start a worker with P7, make P8 authoritative and then attempt a rollback or resume. Observe which policy governs durable acceptance and whether the record exposes the mismatch. The eight local cases do not cover that deployment sequence. Keep its result open until the actual system has been exercised.

The same discipline applies after an urgent revocation. A repair that restores request processing but also restores compromised authority has changed the incident's security outcome. Ask the incident owner to define the permitted recovery state, including which credentials remain unusable. Retain the decision with the release evidence so a later team does not mistake restored traffic for completed remediation.

Request evidence for the actual deployment path

Start the acceptance exercise at the ingress rather than at an isolated helper function. Supply a representative synthetic request through the real gateway and middleware chain. Show that the verifier receives the intended bytes. Repeat with the same JSON values in a changed representation. Preserve expected and observed outcomes under the exact adapter build used for the test.

Then exercise the supported secret-change procedure. Identify what the publisher signs at each step and what the receiver considers eligible. Confirm new-key acceptance before the ordinary retirement decision when the approved procedure permits that sequence. Demonstrate that a retired key is no longer accepted at the specified boundary. If that cannot be observed, retain the missing evidence as an open issue.

Include the surrounding failure path. Check unsupported signature data and unavailable configuration without authorizing an applicant change. Observe the external response and the internal sanitized record. The local R-73 categories are illustrative labels, not a requirement to expose those exact strings in an API response or to replace an established incident classification.

Give the receiving operations team a repeatable question: which delivery, under which policy, using which eligible key reference, supported by which evidence? They should be able to answer from retained records after the process has restarted. A green deployment screen or a successful provider dashboard test cannot answer every part of that question on its own.

Scope the missing authentication work explicitly

For a blockchain application whose access workflow depends on KYC callbacks, Pharos Production's blockchain integration development services provide a relevant implementation route. Scope the work around an adapter specification, the raw-body boundary and an enforceable key-retirement decision. Request test evidence for the deployed path rather than accepting a generic claim that webhook support is included.

The NextCheck KYC platform case study describes Pharos Production's onboarding and verification engineering context. It does not establish that R-73 represents that project, that the project uses the provider discussed here or that its rotation behavior passed these checks. Keep published project context separate from the new acceptance evidence your own integration needs.

Attach the nine-field record and the unresolved provider questions to the implementation brief. Name who owns signing configuration and who can authorize retirement. Before accepting the capability, ask the team to trace one delivery across the original-byte capture, verification and durable acceptance boundaries, with each conclusion linked to an observable result.

‍

Sources and scope

Sources checked October 30, 2026. R-73 is an original synthetic, in-memory, sequential calculation. No provider delivery, middleware, concurrency or client security assessment was performed. The proposed deployment checks remain unexecuted.

About the author

Portrait of Dmytro Nasyrov wearing a dark suit and light blue shirt against a dark background.
Dmytro Nasyrov. Photo supplied by the author.

Written by Dmytro Nasyrov PhD, software architect with 24 years of production experience. Dmytro is the founder and CTO of Pharos Production. He works on production software architecture for FinTech, AI, Web3 and blockchain systems.