Heartank®Developersអ្នកអភិវឌ្ឍន៍Log inចូល

Confirm with HeartBank®

One passkey press for one named action, by a person your app already knows by its own ID for them. Your app gets a yes it can check once; nothing is kept.ការចុច passkey មួយសម្រាប់សកម្មភាពដែលមានឈ្មោះមួយ ដោយមនុស្សដែលកម្មវិធីរបស់អ្នកស្គាល់តាម ID ផ្ទាល់ខ្លួនរបស់វា។ កម្មវិធីរបស់អ្នកទទួលបានចម្លើយយល់ព្រម ដែលវាអាចពិនិត្យបានតែម្តង គ្មានអ្វីត្រូវបានរក្សាទុកទេ។

1 Ask១ ស្នើ
Your server asks for a ticket, with its own ID for the person and the action's fingerprint.ម៉ាស៊ីនមេរបស់អ្នកស្នើសុំសំបុត្រមួយ ជាមួយ ID ផ្ទាល់ខ្លួនរបស់វាសម្រាប់មនុស្សនោះ និងស្នាមម្រាមដៃរបស់សកម្មភាព។
2 The person presses២ មនុស្សនោះចុច
Their browser opens sign.333.eco, which shows your app's registered name and what is being confirmed. They press their own passkey.កម្មវិធីរុករករបស់ពួកគេបើក sign.333.eco ដែលបង្ហាញឈ្មោះដែលបានចុះបញ្ជីរបស់កម្មវិធីអ្នក និងអ្វីដែលកំពុងត្រូវបានបញ្ជាក់។ ពួកគេចុច passkey ផ្ទាល់ខ្លួន។
3 Redeem៣ ប្តូរយក
The browser comes back to your page with a result, or a decline. Your server redeems the result, once.កម្មវិធីរុករកត្រឡប់មកទំព័ររបស់អ្នកវិញ ជាមួយលទ្ធផល ឬការបដិសេធ។ ម៉ាស៊ីនមេរបស់អ្នកប្តូរយកលទ្ធផលនោះ តែម្តងប៉ុណ្ណោះ។

Metered to your app, never to the person. The press page shows only words HeartBank® wrote: your app's registered name and a kind of action, never text your app supplies.គិតតាមការប្រើពីកម្មវិធីរបស់អ្នក មិនដែលពីមនុស្សនោះទេ។ ទំព័រចុចបង្ហាញតែពាក្យដែល HeartBank® សរសេរប៉ុណ្ណោះ៖ ឈ្មោះដែលបានចុះបញ្ជីរបស់កម្មវិធីអ្នក និងប្រភេទសកម្មភាព មិនដែលជាអត្ថបទដែលកម្មវិធីរបស់អ្នកផ្តល់ឱ្យទេ។

ឯកសារយោងខាងក្រោមមានជាភាសាអង់គ្លេសតែប៉ុណ្ណោះ។

Confirm with HeartBank®: reference

A person's own passkey press for one action your app names: a human in the loop, so you know a person, not a script, did it.

Read from the live code: login.333.eco (src/confirm.ts, the request and the redeem) and sign.333.eco (/confirm, the page where the person presses). The press made into a product: the one thing an agent can do nothing about.

The flow: (1) your server asks POST /confirm/request with ITS OWN sub for the person (from Log in with HeartBank®), a kind, the action's SHA-256 and one of your registered redirect URLs; login answers with a ticket. (2) Send the person's browser to https://sign.333.eco/confirm#ticket=…&state=…; the page shows your app's REGISTERED name and what is being confirmed, and asks for that person's own passkey, with the device's own check. (3) The browser comes back to your page with #confirm=<result>&state=…, or #declined=1&state=…. (4) Your server redeems the result at POST /confirm/redeem: once, for your app only, and only if the passkey pressed is that person's.

⚠️ What it proves, and no more: the holder of this person's passkey pressed it, with their device's own check, for this action, just now. That is presence, not personhood: never present a confirmed person as "verified" (a PIN can satisfy the check, and a passkey can be shared or synced).

⛔ A closed list of kinds (today: upload): your app never puts its own words on sign.333.eco, so the page can't be turned into a form that says something else. Nothing is kept: no public record and no history line. For a file the person should put their name to, in public and for good, use Sign with HeartBank® instead.

Any registered app may use it, for a person who has logged in to it with Log in with HeartBank® (a person who last logged in before 2026-11-11 is found from their next log-in).

POST https://login.333.eco/confirm/request

Step 1: ask a person to confirm one action

Who may call it: Sent as FORM fields, not a header: client_assertion_type = urn:ietf:params:oauth:client-assertion-type:jwt-bearer and client_assertion = an ES256 JWT, iss = sub = your client id, aud = the endpoint's own address, one-use jti. The same keys as your Log in with HeartBank® client.

Body application/x-www-form-urlencoded

FieldTypeWhat it is
client_assertion_typestring
client_assertionstring
substringYour app's own sub for the person, from their Log in with HeartBank® ID token.
kind"upload"What they are confirming, from a closed list: the page says it in its own words.
hash optionalstringOptional: the SHA-256 of the thing (a file being uploaded), 64 lowercase hex. The page shows its first eight.
return_tostringOne of your app's registered redirect URLs.

Answers

  • 200 The ticket. { ticket, url, expires_in }
  • 400 bad_request: a kind outside the list, a malformed hash, or a return_to your app never registered. { error, why }
  • 401 invalid_client: the client assertion was refused. { error, why }
  • 404 not_found: the one answer for a sub nobody holds, a person who hasn't logged in to your app since 2026-11-11, and one with no passkey known. { error, why }

What a success answer holds

FieldTypeWhat it is
ticketstringOpaque to your app; put it in the page's address fragment, never a query.
urlstringhttps://sign.333.eco/confirm
expires_inintegerSeconds the ticket lasts (600).

On the MCP server: not a tool: needs the app's own private key on its server; ⛔ the MCP server never holds or passes a developer's key

GET https://sign.333.eco/confirm

Step 2: the page where the person presses

Put the ticket and your state in the FRAGMENT: https://sign.333.eco/confirm#ticket=…&state=…. The passkey sheet opens on arrival. The browser returns to your return_to with #confirm=<result>&state=…, or #declined=1&state=…. ⛔ Never time them or nudge them on your side while they decide.

Who may call it: anyone, no key

Answers

  • 200 The confirm page (HTML).

On the MCP server: not a tool: a browser step: the person's own press

POST https://login.333.eco/confirm/redeem

Step 3: redeem the result, once

Then check sub is the person you asked about and hash is the file you hold, before you accept the action.

Who may call it: Sent as FORM fields, not a header: client_assertion_type = urn:ietf:params:oauth:client-assertion-type:jwt-bearer and client_assertion = an ES256 JWT, iss = sub = your client id, aud = the endpoint's own address, one-use jti. The same keys as your Log in with HeartBank® client.

Body application/x-www-form-urlencoded

FieldTypeWhat it is
client_assertion_typestring
client_assertionstring
resultstringThe confirm value the browser brought back.

Answers

  • 200 Confirmed. { confirmed, sub, kind, hash, at }
  • 400 bad_result (not sign.333.eco's, wrong audience, missing claims, too old) or replayed (already redeemed). { error, why }
  • 401 invalid_client. { error, why }
  • 403 not_yours (made for another app) or not_this_person (the passkey pressed is not the person you asked about). { error, why }

What a success answer holds

FieldTypeWhat it is
confirmedtrue
substringYour app's own sub for the person who pressed: check it is the person you asked about.
kind"upload"
hash optionalstringThe SHA-256 you asked about: check it is the file you hold.
atintegerWhen they pressed (seconds since 1970).

On the MCP server: not a tool: needs the app's own private key on its server; ⛔ the MCP server never holds or passes a developer's key