Payment credential — the client's response to a 402 challenge.
A credential is sent in the Authorization: Payment <base64url JSON> header
after the client has fulfilled payment. It echoes the original challenge
parameters alongside method-specific payment proof, enabling the server to
verify both the challenge binding (via MPP.Challenge.verify/2) and the
payment itself (via the method module).
Wire Format
The base64url-decoded JSON contains:
%{
"challenge" => %{...}, # Echoed challenge parameters
"payload" => %{...}, # Method-specific payment proof
"source" => "did:..." # (optional) Payer identifier
}The challenge.request field remains as its raw base64url string — never
re-serialized — to preserve the exact bytes used in HMAC computation.
Hash credentials
Several payment methods (Tempo, EVM, Hedera, Stellar, Near Intents) accept
payload.type = "hash": the client broadcasts a transfer and presents the
confirmed transaction identifier. That type was backfilled as first-class in
the EVM spec (draft-evm-charge-00 §1.3, mpp-specs #261) alongside
permit2 / authorization / transaction.
decode/1 still treats payload as an opaque map — the same as mppx
Credential.deserialize and mpp-rs PaymentCredential.payload. Typed hash
access is hash_payload/1 (construct) and parse_hash_payload/1 (extract),
matching mpp-rs PaymentPayload::hash / charge_payload(). Method-specific
hash format (0x-prefixed 32-byte hex vs chain-native) is verified by the
method, not here. MPP.Verifier rejects a well-formed hash payload when the
method's credential_types/0 does not include "hash".
Fields
challenge— echoedMPP.Challengestruct from the 402 responsepayload— method-specific payment proof (opaque map at this layer)source— (optional) payer identifier, recommended as DID format
API Functions
| Function | Arity | Description | Param Kinds |
|---|---|---|---|
parse_hash_payload | 1 | Extract the hash from a type="hash" payload (mpp-rs PaymentPayload / charge_payload()). | payload: value |
hash_payload | 1 | Build a type="hash" credential payload map (mpp-rs PaymentPayload::hash). | hash: value |
encode | 1 | Encode a credential to a base64url JSON string (no padding) for the Authorization header. | credential: value |
decode | 1 | Decode a base64url JSON string into a credential with echoed challenge validation. | encoded: value |
Summary
Functions
Decode a base64url JSON string into a credential with echoed challenge validation.
Encode a credential to a base64url JSON string (no padding) for the Authorization header.
Build a type="hash" credential payload map (mpp-rs PaymentPayload::hash).
Extract the hash from a type="hash" payload (mpp-rs PaymentPayload / charge_payload()).
Types
@type t() :: %MPP.Credential{ challenge: MPP.Challenge.t(), payload: map(), source: String.t() | nil }
Functions
Decode a base64url JSON string into a credential with echoed challenge validation.
Parameters
encoded- Base64url-encoded JSON credential string (value)
Returns
{:ok, credential} on success, {:error, reason} on failure (tagged_tuple)
Errors
:invalid_base64:invalid_json:missing_required_fields:invalid_optional_field:empty_id:invalid_method:invalid_request:invalid_digest
Composes With
encode
# descripex:contract
%{
params: %{
encoded: %{
description: "Base64url-encoded JSON credential string",
kind: :value
}
},
errors: [:invalid_base64, :invalid_json, :missing_required_fields,
:invalid_optional_field, :empty_id, :invalid_method, :invalid_request,
:invalid_digest],
returns: %{
type: :tagged_tuple,
description: "`{:ok, credential}` on success, `{:error, reason}` on failure"
},
composes_with: [:encode]
}
Encode a credential to a base64url JSON string (no padding) for the Authorization header.
Parameters
credential- Credential struct to encode (value)
Returns
Base64url-encoded JSON string (string)
Composes With
decode
# descripex:contract
%{
params: %{
credential: %{description: "Credential struct to encode", kind: :value}
},
returns: %{type: :string, description: "Base64url-encoded JSON string"},
composes_with: [:decode]
}
Build a type="hash" credential payload map (mpp-rs PaymentPayload::hash).
Parameters
hash- Transaction hash or chain-native identifier string (value)
Returns
Map with type=hash and hash — no signature field (map)
Composes With
parse_hash_payloadencode
# descripex:contract
%{
params: %{
hash: %{
description: "Transaction hash or chain-native identifier string",
kind: :value
}
},
returns: %{
type: :map,
description: "Map with type=hash and hash — no signature field"
},
composes_with: [:parse_hash_payload, :encode]
}
@spec parse_hash_payload(t() | map()) :: {:ok, String.t()} | {:error, :missing_hash | :not_hash_payload}
Extract the hash from a type="hash" payload (mpp-rs PaymentPayload / charge_payload()).
Parameters
payload- Credential struct or payload map (value)
Returns
{:ok, hash} on a hash payload, {:error, reason} otherwise (tagged_tuple)
Errors
:missing_hash:not_hash_payload
Composes With
hash_payload
# descripex:contract
%{
params: %{
payload: %{description: "Credential struct or payload map", kind: :value}
},
errors: [:missing_hash, :not_hash_payload],
returns: %{
type: :tagged_tuple,
description: "`{:ok, hash}` on a hash payload, `{:error, reason}` otherwise"
},
composes_with: [:hash_payload]
}