# How UCP Message Signatures Verify Webhooks: Complete Technical Guide

> Secure your webhooks with UCP message signatures Learn how to use ECDSA and HTTP Message Signatures to verify payloads and protect your platform

- Repository: [Universal Commerce Protocol (UCP)/ucp](https://github.com/Universal-Commerce-Protocol/ucp)
- Tags: deep-dive
- Published: 2026-04-26

---

**UCP message signatures secure webhook notifications by mandating HTTP Message Signatures with ECDSA over specific request components, requiring platforms to validate Content-Digest, resolve public keys from UCP profiles, and verify business ownership before accepting payloads.**

The Universal Commerce Protocol (UCP) standardizes server-initiated webhook security through **UCP message signatures** implemented via the HTTP Message Signatures specification. As defined in the source code, every webhook payload must carry a valid cryptographic signature that platforms verify through a rigorous sequence of header validation and key resolution steps.

## Required Headers for UCP Message Signatures

The webhook sender must include four specific headers defined in [`docs/specification/order.md`](https://github.com/Universal-Commerce-Protocol/ucp/blob/main/docs/specification/order.md) (lines 46-49):

- **`UCP-Agent`**: An RFC 8941 dictionary containing the business profile URL (`/.well-known/ucp`)
- **`Signature-Input`**: Lists signed components including `@method`, `@authority`, `@path`, `content-digest`, and `content-type`
- **`Signature`**: The ECDSA signature value encoded in raw `r||s` format
- **`Content-Digest`**: SHA-256 hash of the raw request body per RFC 9530

## Step-by-Step UCP Message Signature Verification

Platforms must implement the verification algorithm described in [`docs/specification/order.md`](https://github.com/Universal-Commerce-Protocol/ucp/blob/main/docs/specification/order.md) (lines 66-85) using the following sequence:

### 1. Parse Signature-Input

Extract the `keyid` and ordered component list from the `Signature-Input` header to determine which public key and request components participate in the signature.

### 2. Resolve the UCP Profile

Fetch the JSON Web Key Set from the URL specified in `UCP-Agent`. The platform retrieves the business profile from the `/.well-known/ucp` endpoint, which contains the `signing_keys` array.

### 3. Locate the Public JWK

Match the `kid` parameter from `Signature-Input` against the `kid` values in the `signing_keys` array to retrieve the correct public key for verification.

### 4. Validate Content-Digest

Recompute the SHA-256 hash over the raw request body and compare it to the `Content-Digest` header value to ensure payload integrity.

### 5. Reconstruct the Signature Base

Build the signature base string using the exact component order, HTTP method, authority, path, and headers specified in `Signature-Input`, following RFC 9421.

### 6. Verify the ECDSA Signature

Validate the raw `r||s` encoded signature from the `Signature` header against the reconstructed base and public key using ECDSA verification.

### 7. Authorize Business Ownership

Per [`docs/specification/order.md`](https://github.com/Universal-Commerce-Protocol/ucp/blob/main/docs/specification/order.md) (lines 89-96), confirm the signing business owns the order referenced in the payload by matching the profile URL against the order's business record.

## Security Controls for UCP Message Signatures

### Mandatory Signature Enforcement

Every webhook payload **must** carry a valid signature according to [`docs/specification/signatures.md`](https://github.com/Universal-Commerce-Protocol/ucp/blob/main/docs/specification/signatures.md) (lines 999-1000). Platforms must reject requests lacking signatures or with invalid cryptography.

### Key Rotation Grace Period

Businesses rotate keys by publishing new JWKs in their profile. Per [`docs/specification/signatures.md`](https://github.com/Universal-Commerce-Protocol/ucp/blob/main/docs/specification/signatures.md) (lines 44-48), platforms must accept old keys for a minimum grace period of 7 days to prevent delivery failures during rotation.

### Replay Attack Prevention

The `Idempotency-Key` header is included in signed components, mitigating replay attacks as specified in [`docs/specification/signatures.md`](https://github.com/Universal-Commerce-Protocol/ucp/blob/main/docs/specification/signatures.md) (lines 62-64).

## Implementation Examples

**Example HTTP webhook request:**

```http
POST /webhooks/ucp/orders HTTP/1.1
Host: platform.example.com
Content-Type: application/json
UCP-Agent: profile="https://merchant.example/.well-known/ucp"
Content-Digest: sha-256=:X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=:
Signature-Input: sig1=("@method" "@authority" "@path" "content-digest" "content-type");keyid="merchant-2026"
Signature: sig1=:MEUCIQDTxNq8h7LGHpvVZQp1iHkFp9+3N8Mxk2zH1wK4YuVN8w...:
 
{"id":"order_abc123","event_id":"evt_123","created_time":"2026-01-15T12:00:00Z"}

```

**Python verification pseudocode:**

```python
def verify_webhook(request):
    # 1. Parse Signature-Input header

    sig_input = parse_signature_input(request.headers["Signature-Input"])
    kid = sig_input.keyid
    components = sig_input.components

    # 2. Resolve signer profile

    profile_url = get_profile_url(request.headers["UCP-Agent"])
    profile = fetch_json(profile_url)
    public_key = find_jwk_by_kid(profile["signing_keys"], kid)
    if not public_key:
        raise VerificationError("key_not_found")

    # 3. Verify Content-Digest (if body is signed)

    if "content-digest" in components:
        expected = "sha-256=:" + base64(sha256(request.body)) + ":"
        if request.headers["Content-Digest"] != expected:
            raise VerificationError("digest_mismatch")

    # 4. Build signature base per RFC 9421

    sig_base = build_signature_base(
        components=components,
        method=request.method,
        authority=request.host,
        path=request.path,
        headers=request.headers,
        keyid=kid,
    )

    # 5. Verify ECDSA signature (raw r||s encoding)

    signature = parse_signature(request.headers["Signature"])
    if not ecdsa_verify(sig_base, signature, public_key):
        raise VerificationError("signature_invalid")

    # 6. Authorization check (order ownership)

    payload = json.loads(request.body)
    if not business_owns_order(profile_url, payload["id"]):
        raise VerificationError("unauthorized_sender")

    return True

```

## Summary

- **Mandatory signing**: All UCP webhooks must include valid HTTP Message Signatures per [`docs/specification/signatures.md`](https://github.com/Universal-Commerce-Protocol/ucp/blob/main/docs/specification/signatures.md) (lines 999-1000)
- **Four critical headers**: `UCP-Agent`, `Signature-Input`, `Signature`, and `Content-Digest` form the verification foundation
- **Seven-step verification**: Parse headers, resolve profiles, locate JWKs, validate digests, rebuild signature bases, verify ECDSA, and authorize business ownership
- **7-day rotation grace**: Old signing keys remain valid for one week after rotation to prevent delivery failures
- **Replay protection**: The `Idempotency-Key` header binds signatures to specific requests, preventing replay attacks

## Frequently Asked Questions

### How does a platform locate the correct public key to verify a UCP message signature?

The platform extracts the profile URL from the `UCP-Agent` header, fetches the `/.well-known/ucp` endpoint, and matches the `keyid` from `Signature-Input` against the `kid` values in the `signing_keys` array returned in the profile.

### What happens if a webhook arrives without a valid UCP message signature?

According to [`docs/specification/signatures.md`](https://github.com/Universal-Commerce-Protocol/ucp/blob/main/docs/specification/signatures.md) (lines 999-1000), platforms must reject any webhook payload that lacks a valid signature or fails cryptographic verification.

### How long are rotated signing keys valid in UCP message signatures?

When businesses publish new JWKs in their profile, platforms must continue accepting the previous key for a minimum grace period of 7 days, as specified in [`docs/specification/signatures.md`](https://github.com/Universal-Commerce-Protocol/ucp/blob/main/docs/specification/signatures.md) (lines 44-48).

### Which request components are included in UCP message signature calculations?

The `Signature-Input` header typically includes `@method`, `@authority`, `@path`, `content-digest`, `content-type`, and `idempotency-key`, ensuring the signature binds to the specific HTTP request and prevents replay attacks.