Summary
Every wallet profile update — users.updateProfile() (CLI: profile set) — fails on staging with HTTP 401 {"error":"invalid signature"} on PUT /users/<cryptoId>/profile. The endpoint is currently unusable.
Where
Isolation (what is NOT the cause)
The signing machinery is fine — these all pass against staging with the same signFreshCanonicalPayload + directory-auth (X-Agent-ID = publicKeyBase64):
card publish, domain primary
registry.updateProfileVisibility (body-canonical identity.profile.visibility)
POST /users/<cryptoId>/email/verification (startEmailVerification) — same /users/* directory auth, and its canonical (user.email.start) includes harnessKey and still verifies.
So: the /users/* directory auth, the freshness/nonce scheme, and including harnessKey in the canonical are all correct. Only the user.profile canonical on the profile-update endpoint is rejected.
Further narrowing (all return 401):
- canonical variants: full 8-field, drop
harnessKey, minimal {cryptoId, displayName}, action: "users.profile", actorType: "agent" filled, drop avatarEmail/actorType.
- both
X-Agent-ID = publicKeyBase64 and X-Agent-ID = cryptoId.
Conclusion
The backend's userProfilePayload (the string it rebuilds to verify) diverges from the SDK's userProfileSignaturePayload (users.ts:105) in the non-harnessKey fields. Since email.start verifies with harnessKey present, harnessKey is not the culprit. Most likely a field the backend includes or excludes that the SDK doesn't mirror — e.g. the stored email on the profile, or a server-side defaulting of actorType/avatarEmail — or a field-name/casing drift. The fix is to realign the two canonical builders field-for-field.
SDK canonical today
canonicalPayload("user.profile", {
actorType, avatarEmail, bio, cryptoId, displayName, harnessKey, link, tags
}) // omitted fields -> null; keys sorted; then "\n{ts}\n{nonce}" appended and signed
Repro
const client = makeClient(config, signer); // staging-api.tiny.place
await client.users.updateProfile(signer.agentId, { displayName: "X" });
// -> HTTP 401 { error: "invalid signature" }
Reproduced on a registered staging agent; resolve @<handle> confirms the wallet exists and other signed writes succeed for the same key.
Impact
Suspected regression window
Commit 2df2d3c (2026-06-15) added harnessKey to the profile canonical — but since email.start verifies with harnessKey, the break is likely in another field of the profile canonical, not harnessKey itself. Needs a field-for-field diff against the backend userProfilePayload.
Summary
Every wallet profile update —
users.updateProfile()(CLI:profile set) — fails on staging withHTTP 401 {"error":"invalid signature"}onPUT /users/<cryptoId>/profile. The endpoint is currently unusable.Where
sdk/typescript/src/api/users.ts→updateProfile+userProfileSignaturePayload(canonicaluser.profile).profile set, and previously as a false failure ofdomain buy(the post-registerrecordHarnesstelemetry write hit this endpoint; mitigated client-side in fix(openclaw): record harness key best-effort so domain buy survives a profile-write failure #88, but the root is here).Isolation (what is NOT the cause)
The signing machinery is fine — these all pass against staging with the same
signFreshCanonicalPayload+ directory-auth (X-Agent-ID = publicKeyBase64):card publish,domain primaryregistry.updateProfileVisibility(body-canonicalidentity.profile.visibility)POST /users/<cryptoId>/email/verification(startEmailVerification) — same/users/*directory auth, and its canonical (user.email.start) includesharnessKeyand still verifies.So: the
/users/*directory auth, the freshness/nonce scheme, and includingharnessKeyin the canonical are all correct. Only theuser.profilecanonical on the profile-update endpoint is rejected.Further narrowing (all return 401):
harnessKey, minimal{cryptoId, displayName},action: "users.profile",actorType: "agent"filled, dropavatarEmail/actorType.X-Agent-ID = publicKeyBase64andX-Agent-ID = cryptoId.Conclusion
The backend's
userProfilePayload(the string it rebuilds to verify) diverges from the SDK'suserProfileSignaturePayload(users.ts:105) in the non-harnessKeyfields. Sinceemail.startverifies withharnessKeypresent,harnessKeyis not the culprit. Most likely a field the backend includes or excludes that the SDK doesn't mirror — e.g. the storedemailon the profile, or a server-side defaulting ofactorType/avatarEmail— or a field-name/casing drift. The fix is to realign the two canonical builders field-for-field.SDK canonical today
Repro
Reproduced on a registered staging agent;
resolve @<handle>confirms the wallet exists and other signed writes succeed for the same key.Impact
profile setunusable.domain buy's harness-key write — patched to best-effort in fix(openclaw): record harness key best-effort so domain buy survives a profile-write failure #88, but profiles still can't be edited).Suspected regression window
Commit
2df2d3c(2026-06-15) addedharnessKeyto the profile canonical — but sinceemail.startverifies withharnessKey, the break is likely in another field of the profile canonical, notharnessKeyitself. Needs a field-for-field diff against the backenduserProfilePayload.