{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":[]},"type":"markdown"},"seo":{"title":"DPoP — sender-constrained tokens","description":"Hyperproof developer resources for custom integrations.","llmstxt":{"hide":false,"sections":[{"title":"Table of contents","includeFiles":["**/*"],"excludeFiles":[]}],"excludeFiles":[]}},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"dpop--sender-constrained-tokens","__idx":0},"children":["DPoP — sender-constrained tokens"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["DPoP (Demonstrating Proof-of-Possession,"," ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://datatracker.ietf.org/doc/html/rfc9449"},"children":["RFC 9449"]},") binds an access"," ","token to a private key that only your application holds. A token obtained with"," ","DPoP is useless to anyone who does not also hold your key, so a leaked token"," ","cannot be replayed. ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["DPoP is optional."]}," If you do not send a DPoP proof, you"," ","get an ordinary ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Bearer"]}," token, which is fine for most integrations."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Use DPoP when your security policy requires proof-of-possession — for example,"," ","when tokens travel across less-trusted networks."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"how-it-works","__idx":1},"children":["How it works"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Your application holds an asymmetric ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["key pair"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["With each request, you send a short-lived ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["DPoP proof"]},": a JWT signed with"," ","your private key. Its header carries the matching public key (JWK)."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Hyperproof ties the access token to the thumbprint of that key"," ","(",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["cnf.jkt"]},"). Each call after establishing the key pair must present a fresh"," ","proof signed by the same key."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Supported proof-signing algorithms: ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["ES256, ES384, ES512, RS256, PS256"]}," ","(ES256 is recommended). A proof is valid for ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["60 seconds"]},", and each proof's"," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["jti"]}," may be used only once."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"the-proof-jwt","__idx":2},"children":["The proof JWT"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Header"]}]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Field"},"children":["Field"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Value"},"children":["Value"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["typ"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["dpop+jwt"]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["alg"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["your signing algorithm, e.g. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ES256"]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["jwk"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["your ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["public"]}," key (public members only)"]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Claims"]}]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Claim"},"children":["Claim"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Meaning"},"children":["Meaning"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["htm"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["HTTP method of the request, e.g. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST"]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["htu"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["HTTP URL of the request (without query or fragment)"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["iat"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["issued-at time (Unix seconds)"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["jti"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["unique random id (replay protection)"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ath"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["base64url SHA-256 hash of the access token — ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["required on API calls"]},"; omit it on the token request"]}]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"step-1--get-a-dpop-bound-token","__idx":3},"children":["Step 1 — Get a DPoP-bound token"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Send a proof for the token endpoint (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["htm=POST"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["htu=…/oauth/token"]},", no"," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ath"]},") in the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["DPoP"]}," header of the token request. The response ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["token_type"]}," ","comes back as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["DPoP"]},"."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"# Python — using PyJWT (cryptography backend)\nimport jwt, time, uuid, hashlib, base64\nfrom cryptography.hazmat.primitives.asymmetric import ec\n\nprivate_key = ec.generate_private_key(ec.SECP256R1())\npublic_numbers = private_key.public_key().public_numbers()\n\ndef b64u(b): return base64.urlsafe_b64encode(b).rstrip(b\"=\").decode()\n\njwk = {\"kty\": \"EC\", \"crv\": \"P-256\",\n       \"x\": b64u(public_numbers.x.to_bytes(32, \"big\")),\n       \"y\": b64u(public_numbers.y.to_bytes(32, \"big\"))}\n\ndef dpop_proof(htm, htu, access_token=None):\n    claims = {\"htm\": htm, \"htu\": htu, \"iat\": int(time.time()), \"jti\": uuid.uuid4().hex}\n    if access_token:\n        claims[\"ath\"] = b64u(hashlib.sha256(access_token.encode()).digest())\n    return jwt.encode(claims, private_key, algorithm=\"ES256\",\n                      headers={\"typ\": \"dpop+jwt\", \"jwk\": jwk})\n\ntoken_url = \"https://api.expent.ai/tprm-api/oauth/token\"\n# POST token_url with header DPoP: dpop_proof(\"POST\", token_url)\n# and form grant_type=client_credentials  ->  token_type \"DPoP\"\n","lang":"python"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"step-2--call-the-api-with-the-dpop-token","__idx":4},"children":["Step 2 — Call the API with the DPoP token"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["On every request, send ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["both"]}," the token (with the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["DPoP"]}," auth scheme, not"," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Bearer"]},") ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["and"]}," a fresh proof that includes ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ath"]},":"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"curl -X POST https://api.expent.ai/tprm-api/vendors/search \\\n  -H \"Authorization: DPoP ACCESS_TOKEN\" \\\n  -H \"DPoP: <proof with htm=POST, htu=…/vendors/search, ath=SHA256(ACCESS_TOKEN)>\" \\\n  -H \"Content-Type: application/json\" -d '{}'\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A missing or invalid proof returns ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["401"]}," with client_code ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["auth.invalid_dpop"]},"."]},{"$$mdtype":"Tag","name":"blockquote","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Tip:"]}," In the interactive reference, every operation shows ready-to-run"," ","request samples in curl, Python, JavaScript, and more. The samples are"," ","generated automatically from the spec."]}]}]},"headings":[{"value":"DPoP — sender-constrained tokens","id":"dpop--sender-constrained-tokens","depth":1},{"value":"How it works","id":"how-it-works","depth":2},{"value":"The proof JWT","id":"the-proof-jwt","depth":2},{"value":"Step 1 — Get a DPoP-bound token","id":"step-1--get-a-dpop-bound-token","depth":2},{"value":"Step 2 — Call the API with the DPoP token","id":"step-2--call-the-api-with-the-dpop-token","depth":2}],"frontmatter":{"seo":{"title":"DPoP — sender-constrained tokens"}},"lastModified":"2026-08-11T17:44:00.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/hyperproof-tprm/dpop","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}