Skip to main content
npayload is launching soon.
npayloadDocs
ASP ProtocolConcepts

Message format

The JSON envelope, field reference, and cryptographic integrity chain

Every ASP message follows a consistent JSON envelope structure. Messages are cryptographically signed and hash-chained to form a tamper-evident conversation record. The format supports both JSON and MessagePack serialization, with JSON as the default.

This page covers the full envelope structure, field-by-field reference, agent URI format, integrity chain, DPoP proof binding, and context objects.

Message envelope

{
  "version": "asp/1.0",
  "messageId": "019478a3-7b2c-7def-8a12-3456789abcde",
  "sessionId": "019478a3-0001-7def-8a12-000000000001",
  "sequenceNumber": 5,
  "timestamp": "2026-03-07T14:30:00.000Z",
  "sender": {
    "agentId": "agent://acme.com/services/procurement",
    "orgId": "org_acme",
    "trustScore": 72.5,
    "dpopProof": "eyJhbGciOiJFUzI1NiIsInR5cCI6ImRwb3Arand0IiwiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYifX0..."
  },
  "recipient": {
    "agentId": "agent://widgets.co/services/sales",
    "orgId": "org_widgets"
  },
  "performative": "PROPOSE",
  "content": {
    "mimeType": "application/json",
    "body": {
      "proposalId": "prop_019478a3",
      "type": "service-agreement",
      "subject": "Widget procurement terms",
      "terms": { "quantity": 5000, "pricePerUnit": 12.50 },
      "validUntil": "2026-03-08T14:30:00.000Z"
    },
    "context": []
  },
  "integrity": {
    "hash": "sha256:a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890",
    "previousHash": "sha256:9f8e7d6c5b4a39281f8e7d6c5b4a39281f8e7d6c5b4a39281f8e7d6c5b4a3928",
    "signature": "ed25519:f7e8d9c0b1a2f7e8d9c0b1a2f7e8d9c0b1a2f7e8d9c0b1a2f7e8d9c0b1a2f7e8d9c0b1a2f7e8d9c0b1a2f7e8d9c0b1a2f7e8d9c0b1a2f7e8d9c0b1a2f7e8"
  },
  "constraints": {
    "maxResponseTimeMs": 5000,
    "maxTokenBudget": 10000,
    "requiredTrustScore": 50.0,
    "allowedPerformatives": ["ACCEPT", "REJECT", "COUNTER"]
  }
}

Every ASP message consists of six sections.

1. Protocol header. The version, messageId, sessionId, sequenceNumber, and timestamp fields identify the message within the protocol and the session timeline.

2. Participants. The sender object identifies who sent the message, including their trust score and DPoP proof. The optional recipient object specifies the intended audience (required in multi-party sessions).

3. Performative. One of the 13 performatives that declares the sender's communicative intent.

4. Content. The content object carries the performative-specific payload, a MIME type, and optional context objects for shared session state.

5. Integrity. The integrity object contains the SHA-256 content hash, link to the previous message's hash, and an Ed25519 signature. Together these form a tamper-evident chain.

6. Constraints. The optional constraints object sets expectations for the response: maximum response time, token budget, required trust score, and allowed performatives.

Field reference

Top-level fields

FieldTypeRequiredDescription
versionstringYesProtocol version. Always "asp/1.0" for this specification.
messageIdstringYesUUID v7, unique per message. The time-ordered nature of UUID v7 provides natural chronological sorting.
sessionIdstringYesUUID v7 identifying the session. Present on all messages within a session.
sequenceNumberintegerYesMonotonically increasing counter per sender per session. Starts at 0 for each sender's first message.
timestampstringYesISO 8601 with milliseconds and UTC suffix. Example: "2026-03-07T14:30:00.000Z".
senderobjectYesIdentity of the message sender. See Sender and recipient.
recipientobjectNoIdentity of the intended recipient. Required in multi-party sessions. Omitted for broadcast.
performativestringYesOne of the 13 performatives.
contentobjectYesThe message payload. See Content object.
integrityobjectYesHash chain and cryptographic signature. See Integrity chain.
constraintsobjectNoResponse constraints. See Response constraints.

Agent identifiers

Agents are identified by URIs using the agent:// scheme:

agent://{org-domain}/{department}/{agent-name}

The three components of an agent URI are:

ComponentDescriptionExample
org-domainThe domain of the organization that operates the agentacme.com
departmentOrganizational unit or functional groupingservices, procurement, ops
agent-nameUnique name within the departmentbuyer-01, monitor-east

Examples:

URIDescription
agent://acme.com/services/procurementA procurement agent operated by acme.com
agent://widgets.co/services/salesA sales agent operated by widgets.co
agent://internal.myorg.com/ops/monitorAn internal monitoring agent

Sender and recipient

FieldTypeRequiredDescription
agentIdstringYesThe agent:// URI of the agent
orgIdstringYesOrganization identifier
trustScorenumberSender onlyThe sender's current composite trust score (0 to 100)
dpopProofstringSender onlyDPoP proof-of-possession token binding this message to the sender's key pair

The trustScore and dpopProof fields are included by the sender for the recipient's verification. The recipient object only requires agentId and orgId.


Content object

The content field carries the performative-specific payload.

FieldTypeRequiredDescription
mimeTypestringYesContent type of the body
bodyobjectYesPerformative-specific payload (see each performative's body schema)
contextarrayNoArray of context objects for shared session state

Supported MIME types

MIME typeUsage
application/jsonDefault. Structured data in JSON format.
application/msgpackBinary-efficient alternative to JSON. Same schema, smaller wire format.
text/plainUnstructured text content.
application/octet-streamRaw binary data. Use sparingly; prefer URI references for large payloads.

Context objects

Context objects attach shared state to messages. They allow agents to build up a common understanding over the course of a session without repeating information in every message.

{
  "context": [
    {
      "type": "schema-contract",
      "id": "ctx_schema_01",
      "version": 1,
      "data": {
        "eventSchema": "https://schemas.acme.com/events/order-placed/v2"
      },
      "ttl": 3600,
      "visibility": "session"
    },
    {
      "type": "negotiation-state",
      "id": "ctx_neg_01",
      "version": 3,
      "data": {
        "currentOffer": "prop_019478b7",
        "roundNumber": 3,
        "concessionsMade": ["latency", "price"]
      },
      "visibility": "session"
    }
  ]
}

Context object fields

FieldTypeRequiredDescription
typestringYesOne of the eight defined context types (see table below)
idstringYesUnique identifier for this context object within the session
versionintegerYesVersion number, incremented when the context is updated
dataobjectYesType-specific payload
ttlintegerNoTime-to-live in seconds. The context expires after this duration.
visibilitystringNo"session" (visible to all participants), "turn" (valid for current exchange only), or "persistent" (survives session close). Defaults to "session".

Defined context types

TypePurpose
schema-contractAgreed-upon data schemas for the session (event formats, API contracts)
agent-cardAgent capability declarations (supported performatives, protocols, trust requirements)
negotiation-stateCurrent negotiation progress (round number, concessions, standing offers)
shared-knowledgeFacts established during the session that both parties agree on
execution-stateProgress of ongoing work (task completion, milestones, metrics)
trust-evidenceTrust-related data (verification results, compliance attestations)
economic-stateFinancial context (budget remaining, costs incurred, escrow status)
customApplication-specific context. Use a namespaced type in the data to avoid collisions.

Integrity chain

Every ASP message includes three integrity fields that together create a tamper-evident, cryptographically verifiable conversation record.

How the hash chain works

Message 0          Message 1          Message 2          Message 3
┌──────────┐       ┌──────────┐       ┌──────────┐       ┌──────────┐
│ hash: H0 │◄──────│prevHash: │◄──────│prevHash: │◄──────│prevHash: │
│ prev: 0s │       │  H0      │       │  H1      │       │  H2      │
│ sig: S0  │       │ hash: H1 │       │ hash: H2 │       │ hash: H3 │
└──────────┘       │ sig: S1  │       │ sig: S2  │       │ sig: S3  │
                   └──────────┘       └──────────┘       └──────────┘

Each message's previousHash points to the preceding message's hash, forming an unbroken chain. If any message is altered, removed, or reordered, the chain breaks and verification fails.

hash

SHA-256 hash of the canonical JSON representation of the content object. Canonical JSON is produced by:

  1. Sorting all object keys lexicographically
  2. Removing all whitespace between tokens
  3. Normalizing Unicode to NFC form
  4. Using minimal numeric representation (no trailing zeros)
sha256:SHA-256(canonicalJSON(content))

previousHash

The hash value of the previous message in the session, creating a chain link. For the first message in a session, previousHash is 64 zero hex characters:

sha256:0000000000000000000000000000000000000000000000000000000000000000

Chain ordering is determined by (timestamp, sender.agentId, sequenceNumber). When two messages have the same timestamp, the sender's agent ID breaks the tie. When both match, the sequence number provides the final deterministic order.

signature

Ed25519 signature computed over the concatenation of the following fields, separated by null bytes (\0):

version \0 sessionId \0 sequenceNumber \0 timestamp \0 sender.agentId \0 performative \0 hash \0 previousHash

The signature binds the message metadata to its content hash, proving that the sender produced this specific message with this specific content at this specific point in the conversation.

Hash chains make the conversation tamper-evident. If any party modifies, reorders, inserts, or removes a message after the fact, the chain breaks. Both participants can independently verify the integrity of the entire conversation by replaying the hash chain from the first message. This is critical for dispute resolution, where the unmodified conversation record serves as evidence.

Verification process

Compute the content hash

Serialize the content object to canonical JSON and compute its SHA-256 hash. Compare the result to the hash field in the message.

Confirm that previousHash matches the hash of the preceding message in the session (or is all zeros for the first message).

Verify the signature

Concatenate the signed fields with null byte separators. Verify the Ed25519 signature using the sender's public key (obtained from their Agent Card during the INTRODUCE phase).

import { verify } from "@noble/ed25519"

function verifyMessage(
  message: AspMessage,
  previousMessage: AspMessage | null,
  senderPublicKey: Uint8Array
): boolean {
  // 1. Verify content hash
  const canonical = canonicalJSON(message.content)
  const computedHash = sha256(canonical)
  if (computedHash !== message.integrity.hash) return false

  // 2. Verify chain link
  const expectedPrevHash = previousMessage
    ? previousMessage.integrity.hash
    : "sha256:" + "0".repeat(64)
  if (message.integrity.previousHash !== expectedPrevHash) return false

  // 3. Verify signature
  const signedData = [
    message.version,
    message.sessionId,
    String(message.sequenceNumber),
    message.timestamp,
    message.sender.agentId,
    message.performative,
    message.integrity.hash,
    message.integrity.previousHash,
  ].join("\0")

  return verify(
    message.integrity.signature,
    new TextEncoder().encode(signedData),
    senderPublicKey
  )
}

DPoP proof structure

Each message from a sender includes a DPoP (Demonstrating Proof-of-Possession) proof that binds the message to the sender's cryptographic key pair. This prevents token replay and impersonation.

The DPoP proof is a JWT with the following structure:

{
  "header": {
    "typ": "dpop+jwt",
    "alg": "ES256",
    "jwk": {
      "kty": "EC",
      "crv": "P-256",
      "x": "...",
      "y": "..."
    }
  },
  "payload": {
    "htm": "ASP",
    "htu": "asp://019478a3-0001-7def-8a12-000000000001",
    "iat": 1741354200,
    "jti": "unique-proof-id",
    "ath": "sha256:..."
  }
}

Understanding the DPoP proof

FieldLocationDescription
typHeaderAlways "dpop+jwt"
algHeaderSigning algorithm. ES256 (ECDSA with P-256) is required.
jwkHeaderThe sender's public key in JWK format
htmPayloadThe method being bound. Always "ASP" for ASP messages.
htuPayloadThe URI being bound. Uses asp://{sessionId} to bind the proof to a specific session.
iatPayloadIssued-at timestamp (Unix seconds). Prevents replay of old proofs.
jtiPayloadUnique proof identifier. Each proof must have a unique jti.
athPayloadAccess token hash. Required when an access token is present. SHA-256 hash of the token.

The jwk in the DPoP proof must match the dpopPublicKey in the sender's Agent Card. If they differ, the message should be rejected. This is verified during the INTRODUCE phase and on every subsequent message.


Response constraints

The optional constraints object lets the sender specify expectations for the response.

FieldTypeDescription
maxResponseTimeMsintegerMaximum time the sender will wait for a response, in milliseconds
maxTokenBudgetintegerSuggested upper bound on the token budget for generating the response. Advisory only.
requiredTrustScorenumberMinimum trust score (0 to 100) the responding agent must have for the response to be considered valid
allowedPerformativesstring[]List of performatives the sender expects in the response
{
  "constraints": {
    "maxResponseTimeMs": 30000,
    "maxTokenBudget": 4096,
    "requiredTrustScore": 50.0,
    "allowedPerformatives": ["ACCEPT", "REJECT", "COUNTER"]
  }
}

Response constraints are advisory. The protocol does not enforce them at the transport level. An agent that ignores constraints may receive lower trust scores over time, but the message itself will still be delivered and processed.


Encoding and size limits

Encoding

FormatDescription
JSONPrimary format. All examples in this documentation use JSON.
MessagePackOptional binary format for bandwidth-sensitive scenarios. Same schema as JSON.
String encodingAll strings are UTF-8.
Binary dataBase64url encoding for any binary values within JSON payloads.

Size limits

LimitValue
Maximum message size1 MiB
Maximum content.body size512 KiB

For payloads that exceed these limits, use a URI reference in the body that points to the full content hosted externally. Include a hash of the referenced content so the recipient can verify integrity after retrieval.

{
  "body": {
    "topic": "result",
    "data": {
      "summary": "Analysis complete. 47,000 records processed.",
      "fullResultUri": "https://storage.acme.com/results/analysis-019478a3.json",
      "fullResultHash": "sha256:e3b0c44298fc1c149afbf4c8996fb924..."
    }
  }
}

Next steps

Was this page helpful?

On this page