curl --request POST \
--url "https://directory.example.com/personas/example.com/transfer/dns-challenges/<challenge-id>/verify" \
--header "Content-Type: application/json" \
--data '{
"action": "transfer_persona",
"persona": "example.com",
"current_operator_did": "did:openpayload:1111111111111111111111",
"new_operator_did": "did:openpayload:2222222222222222222222",
"new_controller_keys": [
"did:openpayload:2222222222222222222222#keys-1"
],
"new_controller_threshold": 1,
"delivery_constraints": {
"requested_cache_seconds": 86400,
"max_http_envelope_bytes": 26214400,
"max_unchunked_message_bytes": 16777216,
"max_chunk_bytes": 2097152,
"max_replicas": 2
},
"challenge_id": "<challenge-id>",
"verification_expires_at": 1796083200000,
"new_operator_signer_key_id": "did:openpayload:2222222222222222222222#keys-1",
"nonce": "<current-operator-nonce>",
"valid_until": "<epoch-milliseconds>",
"signer_key_id": "did:openpayload:1111111111111111111111#keys-1"
}'
Personas
Verify a Persona transfer challenge
Verify fresh DNS control and prepare the new operator acceptance payload
curl --request POST \
--url "https://directory.example.com/personas/example.com/transfer/dns-challenges/<challenge-id>/verify" \
--header "Content-Type: application/json" \
--data '{
"action": "transfer_persona",
"persona": "example.com",
"current_operator_did": "did:openpayload:1111111111111111111111",
"new_operator_did": "did:openpayload:2222222222222222222222",
"new_controller_keys": [
"did:openpayload:2222222222222222222222#keys-1"
],
"new_controller_threshold": 1,
"delivery_constraints": {
"requested_cache_seconds": 86400,
"max_http_envelope_bytes": 26214400,
"max_unchunked_message_bytes": 16777216,
"max_chunk_bytes": 2097152,
"max_replicas": 2
},
"challenge_id": "<challenge-id>",
"verification_expires_at": 1796083200000,
"new_operator_signer_key_id": "did:openpayload:2222222222222222222222#keys-1",
"nonce": "<current-operator-nonce>",
"valid_until": "<epoch-milliseconds>",
"signer_key_id": "did:openpayload:1111111111111111111111#keys-1"
}'
After publishing the transfer TXT record, submit the complete unsigned transfer intent. The Directory validates it against finalized state, verifies DNS, and returns an attested
The new operator signs the decoded
acceptance_payload for the new operator to sign.
This step does not transfer the Persona. Decode and sign the exact acceptance_payload bytes; do not sign the hexadecimal characters or reconstruct the payload locally.
Request
string
required
Canonical Persona being transferred.
string
required
Transfer challenge ID.
string
required
Operator DID currently registered for the Persona.
string
required
Active DID accepting the Persona.
string[]
required
One through eight unique signing-key IDs published by
new_operator_did.integer
required
Must equal
1 in Persona v1.string
required
Key that will sign
acceptance_payload. It must be one of new_controller_keys.string
required
Must be
transfer_persona.string
required
Current operator nonce returned with the challenge.
string
required
Short-lived Unix-millisecond or ISO-8601 expiration for the transfer authorization.
string
required
Active current Persona controller that will sign the final transfer payload.
object
New Persona delivery constraints. Omit
effective_ttl_seconds. If omitted, the transferred Persona has no Persona-specific constraints.integer
required
Unix-millisecond expiration for the renewed DNS verification. It must be in the future and cannot be more than 90 days away.
persona and challenge_id may also be repeated in the body. When supplied, they must match the path.
curl --request POST \
--url "https://directory.example.com/personas/example.com/transfer/dns-challenges/<challenge-id>/verify" \
--header "Content-Type: application/json" \
--data '{
"action": "transfer_persona",
"persona": "example.com",
"current_operator_did": "did:openpayload:1111111111111111111111",
"new_operator_did": "did:openpayload:2222222222222222222222",
"new_controller_keys": [
"did:openpayload:2222222222222222222222#keys-1"
],
"new_controller_threshold": 1,
"delivery_constraints": {
"requested_cache_seconds": 86400,
"max_http_envelope_bytes": 26214400,
"max_unchunked_message_bytes": 16777216,
"max_chunk_bytes": 2097152,
"max_replicas": 2
},
"challenge_id": "<challenge-id>",
"verification_expires_at": 1796083200000,
"new_operator_signer_key_id": "did:openpayload:2222222222222222222222#keys-1",
"nonce": "<current-operator-nonce>",
"valid_until": "<epoch-milliseconds>",
"signer_key_id": "did:openpayload:1111111111111111111111#keys-1"
}'
Response
200 Response
{
"challenge_id": "<challenge-id>",
"persona": "example.com",
"action": "transfer_persona",
"challenge_nonce": "3",
"operator_nonce": "7",
"record_name": "_openpayload-persona.example.com",
"record_type": "TXT",
"record_value": "openpayload-persona-v1=<challenge>",
"expires_at": "2026-09-17T18:00:00Z",
"current_operator_did": "did:openpayload:1111111111111111111111",
"new_operator_did": "did:openpayload:2222222222222222222222",
"status": "verified",
"dns_proof_hash": "0x<64-hex-dns-proof-hash>",
"verification_expires_at": 1796083200000,
"acceptance_payload": "0x<new-operator-acceptance-payload>",
"attestor": "0x<directory-attestor-account>",
"attestation_signature": "0x<directory-attestation-signature>"
}
acceptance_payload with new_operator_signer_key_id, then sends that signature and the exact echoed verification_expires_at to the prepare endpoint.
Straight to the point
POST /personas/{persona}/transfer/dns-challenges/{challenge_id}/verify
- Verifies the fresh Persona TXT record
- Validates both DIDs, both controller roles, constraints, nonces, and expiration
- Returns the payload the new operator must sign
- Does not consume the challenge or submit the transfer

