> ## Documentation Index
> Fetch the complete documentation index at: https://docs.openpayload.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Shared policies and control actions

> Policy V3 structure for DID, Persona, and Application scopes

Policy V3 is available on the production chain and the USE2 OpenDispatch Directory, Relay, and Cache services. The spec 123 Java deployment includes Application authorization, `RelayAccepted`, and Persona release profile validation. [Policy V2](/concepts/delivery-policies) 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.

| Field | Meaning |
| - | - |
| `policy_id` | Blake2b-256 of `openpayload:delivery-policy:v3:` followed by SCALE-encoded scope |
| `scope` | Object with `type` set to `DID`, `PERSONA`, or `APPLICATION`, and an `id` string |
| `revision` | Starts at 1 and increases for each complete replacement |
| `operator_did` | Scope owner: DID itself, Persona operator, or Application control DID |
| `ruleset` | Ordered rules with unique IDs, priorities, conditions, and actions |
| `policy_ttl_seconds` | 1–300 seconds for Relay and Cache policy lookup caching; 60 by default |

`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 retains `TagEquals`, `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 with `RequireProfile`, followed by a `Forward` or `Store` step. A compatible client [resolves the delivery target](/api-reference/directory/dids/delivery-resolution), 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.

```json theme={null}
{
  "action": "sendPush",
  "recipient": {"$from": "/event/message_recipient_did"},
  "app_signature": {"$from": "/envelope/context/app_signature"}
}
```

The application defines and verifies `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:

```json theme={null}
{
  "type": "DeliveryPlan/v1",
  "entry_step": "api",
  "steps": [
    {
      "id": "api",
      "operation": "Call",
      "service_did": "did:openpayload:1111111111111111111111",
      "service_id": "did:openpayload:1111111111111111111111#control",
      "registered_domain": "push.example.com",
      "payload_template": {"action": "sendPush", "recipient": {"$from": "/event/message_recipient_did"}}
    },
    {
      "id": "fallback",
      "operation": "Send",
      "recipient_did": "did:openpayload:1111111111111111111111",
      "payload_template": {"action": "queuePush", "message_id": {"$from": "/event/message_id"}}
    }
  ],
  "transitions": [{"from": "api", "to": "fallback", "trigger": "Failure", "mode": "Next"}]
}
```

Domain proof uses the existing Persona DNS challenge. It is unrelated to the envelope's `tag`.

## Talaria Chat example

When Talaria Chat sends an encrypted message, its client can set `application_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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.