messpunkt.io Partner API · DPoP
Preview V1 in active development — target release Q2 2026. Become a pilot partner: kontakt@messpunkt.io

DPoP — proof of possession

The messpunkt.io production API will require DPoP (RFC 9449) on every request. A stolen access token is useless without the private key the token is bound to. The sandbox currently accepts plain Bearer tokens as well, so Swagger UI and curl work without extra setup — but partners should implement DPoP before GA.

What DPoP does in one paragraph

Your client generates an ECDSA (ES256) or RSA (RS256) key pair once and keeps it in memory or a secure store. On every API call, you sign a short-lived JWT — the DPoP proof — that contains the HTTP method, full URL, a unique jti, and the current timestamp. You send it in a DPoP header alongside the access token. The server checks the signature against the public-key thumbprint bound into the access token (cnf.jkt claim). If they match, the request is authentic.

Request shape

GET /v1/whoami HTTP/2
Host: api.sandbox.messpunkt.io
Authorization: DPoP eyJhbGciOiJSUzI1NiIs...   ← the access token
DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZy...       ← the proof, a separate JWT

Proof contents

HeaderValue
typdpop+jwt
algES256 (recommended) or RS256
jwkThe public key in JWK form
ClaimValue
jtiRandom UUID — unique per request
htmRequest method, e.g. GET
htuRequest URL without query string, e.g. https://api.sandbox.messpunkt.io/v1/whoami
iatCurrent Unix time (seconds)
athBase64url(SHA-256(access_token)) — access-token hash

Working code

Same request, four languages. Pick your stack, copy, adapt.

// dotnet add package Microsoft.IdentityModel.JsonWebTokens
using System.Security.Cryptography;
using System.Text;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;

var ec  = ECDsa.Create(ECCurve.NamedCurves.nistP256);
var key = new ECDsaSecurityKey(ec);
var jwk = JsonWebKeyConverter.ConvertFromECDsaSecurityKey(key);
jwk.Use = "sig"; jwk.Alg = "ES256";

string Proof(string method, string url, string accessToken)
{
    using var sha = SHA256.Create();
    var ath = Base64UrlEncoder.Encode(
        sha.ComputeHash(Encoding.UTF8.GetBytes(accessToken)));

    var descriptor = new SecurityTokenDescriptor {
        Claims = new Dictionary<string, object> {
            ["jti"] = Guid.NewGuid().ToString("N"),
            ["htm"] = method,
            ["htu"] = url,
            ["iat"] = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
            ["ath"] = ath,
        },
        SigningCredentials = new SigningCredentials(
            key, SecurityAlgorithms.EcdsaSha256),
        TokenType = "dpop+jwt",
        AdditionalHeaderClaims = new Dictionary<string, object> {
            ["jwk"] = new { kty = jwk.Kty, crv = jwk.Crv, x = jwk.X, y = jwk.Y }
        },
    };
    return new JsonWebTokenHandler().CreateToken(descriptor);
}

var method = HttpMethod.Get;
var url    = "https://api.sandbox.messpunkt.io/v1/whoami";
var accessToken = "...";  // from /oauth/token

var req = new HttpRequestMessage(method, url);
req.Headers.Add("Authorization", $"DPoP {accessToken}");
req.Headers.Add("DPoP", Proof(method.Method, url, accessToken));

var http = new HttpClient();
var res  = await http.SendAsync(req);
Console.WriteLine(await res.Content.ReadAsStringAsync());
// npm i jose
import { generateKeyPair, SignJWT, exportJWK, calculateJwkThumbprint } from 'jose';
import { createHash, randomUUID } from 'crypto';

const { privateKey, publicKey } = await generateKeyPair('ES256', { extractable: true });
const publicJwk = await exportJWK(publicKey);
publicJwk.alg = 'ES256';

async function dpopProof(method, url, accessToken) {
  const ath = createHash('sha256').update(accessToken).digest('base64url');

  return await new SignJWT({
      jti: randomUUID(),
      htm: method,
      htu: url,
      iat: Math.floor(Date.now() / 1000),
      ath,
    })
    .setProtectedHeader({ alg: 'ES256', typ: 'dpop+jwt', jwk: publicJwk })
    .sign(privateKey);
}

const accessToken = '...';  // from /oauth/token
const url    = 'https://api.sandbox.messpunkt.io/v1/whoami';
const proof  = await dpopProof('GET', url, accessToken);

const res = await fetch(url, {
  headers: {
    Authorization: `DPoP ${accessToken}`,
    DPoP: proof,
  },
});
console.log(await res.json());
# pip install authlib httpx
from authlib.jose import JsonWebKey, jwt
from cryptography.hazmat.primitives.asymmetric import ec
from hashlib import sha256
import base64, time, uuid, httpx

priv = ec.generate_private_key(ec.SECP256R1())
jwk_priv = JsonWebKey.import_key(
    priv.private_bytes_raw() if False else priv, {"kty": "EC", "crv": "P-256", "alg": "ES256", "use": "sig"})
jwk_pub  = jwk_priv.as_dict(is_private=False)

def dpop_proof(method: str, url: str, access_token: str) -> str:
    ath = base64.urlsafe_b64encode(sha256(access_token.encode()).digest()).rstrip(b"=").decode()
    header = {"typ": "dpop+jwt", "alg": "ES256", "jwk": jwk_pub}
    payload = {
        "jti": str(uuid.uuid4()),
        "htm": method,
        "htu": url,
        "iat": int(time.time()),
        "ath": ath,
    }
    return jwt.encode(header, payload, jwk_priv).decode()

url = "https://api.sandbox.messpunkt.io/v1/whoami"
access_token = "..."  # from /oauth/token

r = httpx.get(url, headers={
    "Authorization": f"DPoP {access_token}",
    "DPoP": dpop_proof("GET", url, access_token),
})
print(r.json())
# needs: openssl, jose-util (https://github.com/latchset/jose) or step-cli

# 1. one-time key generation
jose jwk gen -i '{"alg":"ES256"}' -o priv.jwk
jose jwk pub -i priv.jwk -o pub.jwk

# 2. per-request proof (swap ACCESS, URL, METHOD)
ACCESS="eyJhbGciOi..."
URL="https://api.sandbox.messpunkt.io/v1/whoami"
METHOD="GET"

ATH=$(printf "%s" "$ACCESS" | openssl dgst -binary -sha256 \
  | openssl base64 | tr -d '=' | tr '/+' '_-')

PAYLOAD=$(jq -c -n \
  --arg jti "$(uuidgen)" --arg htm "$METHOD" --arg htu "$URL" \
  --arg ath "$ATH" --argjson iat "$(date +%s)" \
  '{jti:$jti, htm:$htm, htu:$htu, iat:$iat, ath:$ath}')

HEADER=$(jq -c -n --slurpfile jwk pub.jwk \
  '{typ:"dpop+jwt", alg:"ES256", jwk:$jwk[0]}')

PROOF=$(jose jws sig -I <(echo "$PAYLOAD") -k priv.jwk \
  -s "$HEADER" -c -o -)

curl -H "Authorization: DPoP $ACCESS" -H "DPoP: $PROOF" "$URL"

Token-endpoint DPoP

The production /oauth/token endpoint will additionally require a DPoP proof when you exchange the code or refresh token. The proof has the same shape minus ath, and the returned access token will carry a cnf.jkt claim bound to the same key. Sandbox currently skips this.

Common pitfalls

Next

Bruno, Swagger UI, and Scalar do not generate DPoP proofs out of the box. Use the sandbox's plain Bearer mode for interactive exploration, and implement DPoP for server-to-server integration. Our production requirement will be announced at least 3 months before it's enforced.

See also: Error handling · Quickstart · Full API reference