RelayAccepted, and Persona release profile validation. Policy V2 remains available for existing integrations.
Policy V3 stores one public policy per scope in a shared map. Its three scope types are DID, PERSONA, and APPLICATION. A recipient DID or addressed Persona policy can select delivery routes. An Application policy can add event-driven Send or Call actions, but cannot alter routing or retention constraints. application_id identifies the registered application; the envelope’s tag remains a separate, application-defined classification that rules may match.
policy_ttl_seconds does not retain messages. DID and Persona delivery constraints use requested_cache_seconds for requested Cache retention.
The policy ID is Blake2b-256(UTF-8("openpayload:delivery-policy:v3:") || SCALE(scope)), encoded as 0x plus 64 lowercase hexadecimal characters. In SCALE, the scope variant is 0 for DID, 1 for Persona, or 2 for Application, followed by the scope ID as a byte vector. The ID stays stable across revisions.
Rules and events
All conditions in a rule must match. The highest numeric priority wins within a policy; ruleset order breaks a tie. Persona rules take precedence over DID rules for an addressed Persona. An applicable Application rule runs independently of the recipient rule. V3 retainsTagEquals, TagPrefix, TagAbsent, RecipientEquals, and RecipientIn. EventEquals can match RelayAccepted, RelayDelivered, or CacheStored. RelayAccepted is queued at Relay ingress after policy and profile checks; it does not mean delivery succeeded. RelayDelivered fires after local recipient delivery; CacheStored fires after durable storage. An event-bound plan contains only Send and Call steps. Routing steps remain in separate plans.
DeliveryPlan/v1 has an entry_step, a list of steps, and directed transitions. A transition selects Success, Failure, or Always, and Next or Fork. This allows a policy to send several control messages or call several services in sequence or parallel. A failed step follows its declared failure path. Receivers should treat call_id and control message IDs as idempotency keys because network retries can repeat an action.
Persona-gated delivery
A Persona policy can start a delivery plan withRequireProfile, followed by a Forward or Store step. A compatible client resolves the delivery target, seals one envelope with openpayload:persona-release:v1, signs it, and sends it to a Relay. The Relay checks the public profile shape and its binding to the selected Persona policy before continuing the plan. A policy can route an encrypted copy to a Persona processor, which may provide a separate unlock part to the recipient under its own rules.
The Relay cannot prove how opaque plaintext was encrypted or enforce a processor’s unlock decision. Recipient software must verify the processor proof and enforce the unlock rule. Applications still need to agree on the format inside the decrypted payload.
Payload templates
Send and Call each carry a public JSON object in payload_template. A field of the form { "$from": "/envelope/context/app_signature" } copies the referenced JSON value without changing its type. Available roots are /envelope/, /event/, /policy/, and /node/. Missing references fail the step. Templates are limited to 4096 UTF-8 bytes, 16 levels, and 256 JSON nodes. Do not place secrets in a public policy.
context.app_signature. OpenPayload transports it as an opaque envelope attribute; it does not claim that the signature proves application origin.
Send and Call
Send names recipient_did and payload_template. The executing node encrypts the rendered payload to that DID’s published X25519 agreement key, signs the new DDN envelope with its node DID root key, and submits it to a Relay. The recipient must validate the envelope signature, trust the sending node for the claimed role, and independently verify any application proof carried in the rendered payload. The node signature alone does not prove that the original delivery event occurred. An Application policy may Send only to its control DID or an explicitly linked DID.
Call names service_did, service_id, registered_domain, and payload_template. The target DID must advertise an OpenPayloadApplicationControlService HTTPS endpoint on that exact domain. Domain authorization requires an active DNS-verified Persona owned by the policy operator. An Application also records the domain separately; its Call step must match it. The Directory and executing node check the domain again before the request. The node rejects nonpublic network addresses and does not follow redirects.
The HTTPS POST body has signed_payload_b64 and signature_b64. Decode the first field as UTF-8 JSON and verify the Ed25519 signature over those exact decoded bytes using the root verification key of its node_did. The signed JSON contains protocol: "openpayload:policy-call:v1", the rendered payload, policy identity and revision, scope, rule and step IDs, event name, original message ID, node_did, control_recipient_did, registered_domain, created_at, expires_at, and an idempotent call_id. The target service must verify the target DID, policy context, freshness, and call_id before acting. It should accept calls only from node DIDs it trusts for the claimed Relay or Cache role. A valid node signature proves which node sent the request; it does not independently prove that the original delivery event occurred. The receiver does not need to reproduce the sender’s JSON canonicalization to verify the signature.
Any HTTP 2xx response completes a Call step. HTTP 408, 429, and 5xx responses can be retried until the signed event expires; other responses take the failure transition. The target should return a 2xx response only after it has durably accepted the action.
For example, a plan can try a registered HTTPS service and then fall back to a DID control message:
tag.
Talaria Chat example
When Talaria Chat sends an encrypted message, its client can setapplication_id: "talaria" and place an application signature in context.app_signature. The Relay still uses the recipient’s DID or Persona routing rules. If the recipient is offline and a Cache stores the message, an Application policy can match CacheStored, render a control payload from public envelope fields, and Send it to a control DID or Call a registered HTTPS service. A separate Talaria service interprets and verifies the control payload before deciding whether to send a push notification. The message payload remains opaque to the network.
The Talaria control receiver is separate from these deployed network services. The example requires its own Application registration, policy, and receiver deployment.
