DRAWING NO. TLR-2026 SHEET 2 / 4 INTERFACE DEEP LINKS ONLY VERSIONING ADDITIVE STATUS IMPLEMENTED ANDROID + iOS
◄  MESSENGER ⇄ WALLET  ►POS. B-01

Link
protocol Everything that goes between a messenger and the GNU Taler wallet — short form

[2] No IPC, no bound service, no content provider. The messenger opens talerlink://…, the wallet answers with <scheme>://taler-return?…. <scheme> is the return scheme of the messenger: sgnl for the official Signal app (default entry upstream), signalfuergnu for the test fork, talermost for Mattermost.

B-02 Links
LinkDirectionPurpose
compose-sendmessenger → walletCreate a payment: send or request, optionally split across a group
compose-refundmessenger → walletRefund a received, accepted payment
compose-abortmessenger → walletCancel the user's own open payments (1 to 32 URIs)
pay-push / pay-pullmessenger → walletAction link: the normal payment URI plus correlationId and returnUri; accept a foreign payment
taler-returnwallet → messengerReturn: status variant for compose-send, compose-refund and action links; result variant for compose-abort
open / taler-openwallet → messengerOpen the chat at the payment message from the wallet's transaction list
messenger                                   wallet
  |  remember(correlationId, intent)           |
  |------ talerlink://compose-* ------------->|  parse, user confirms
  |------ talerlink://pay-push/...?… -------->|  read and strip query, normal URI flow
  |<----- <scheme>://taler-return?… ---------|  target verified, query rebuilt
  take(correlationId)  (single use, 15 min)
  re-check against the exchange, send message / draft / update status
B-03 compose-send
MESSENGER ASKS THE WALLET TO CREATE A PAYMENT

Amount, currency and subject are asked by the wallet; the messenger knows no amounts. The user picks send or request in the wallet; direction is only the initial position of the toggle.

ParameterRequiredMeaning
recipientHintnoDisplay name of the chat. Display only
senderAppNamenoName of the messenger, e.g. Signal. Used only for the subject proposal send via <name>: <hint>
isGroupnotrue or false
directionnoPAY_PUSH (send, default) or PAY_PULL (request)
disappearingMessagesSecondsnoTimer of the chat, 0 = off
correlationIdyesLowercase UUID, new per operation, echoed back unchanged
returnUriyesExactly <scheme>://taler-return, no path, query or fragment
memberCountgroupsGroup size according to the messenger. A hint, the wallet does not rely on it
avatarUrinoAndroid: content:// URI of the chat avatar with a temporary read grant. The wallet copies a scaled-down version
avatarFilenoiOS: plain file name <correlationId>.jpg in a shared app group folder. Name checked, max. 512 KB, source deleted after copying
talerlink://compose-send?recipientHint=Anna%20M%C3%BCller&senderAppName=Signal
  &isGroup=false&direction=PAY_PUSH&disappearingMessagesSeconds=0
  &correlationId=6f1c2a34-8b1e-4d7a-9c55-0e8f3b2a1d90
  &returnUri=sgnl%3A%2F%2Ftaler-return

Returns the status variant with status=ready, talerUri and talerPaymentData (one URI for a single payment, N for a group split), or status=cancelled if the user leaves before a payment exists.

B-04 compose-refund · compose-abort · action link
COMPOSE-REFUND

originalUri, correlationId, returnUri. The wallet resolves the received transaction itself and prefills amount and subject. It warns if the payment was already refunded. Returns ready + talerUri.

COMPOSE-ABORT

v, repeated uri (1 to 32), optional display metadata (reason, summary, totalAmount, …), correlationId, returnUri. One confirmation for all. Metadata is never used for decisions; the wallet warns when it disagrees with its own transactions.

ACTION LINK

talerlink://pay-push/<rest>?correlationId=…&returnUri=…. The wallet reads both parameters, strips the whole query and hands the clean taler:// URI to wallet-core. Own URI: opens the own transaction instead of the accept screen.

B-05 Return
STATUS VARIANT
<scheme>://taler-return?correlationId=<id>
  &status=ready|cancelled
  [&talerUri=<uri>]
  [&talerPaymentData=<json>]

For compose-send, compose-refund and action links. For action links status is only a trigger: the messenger re-checks the payment at the exchange.

RESULT VARIANT (COMPOSE-ABORT)
<scheme>://taler-return?correlationId=<id>
  &result=<uri>%7Caborted
  &result=<uri>%7Calready_claimed

One result per input URI, in input order: aborted, already_claimed, expired, declined, not_own, failed. Split at the first |.

OPEN THE CHAT FROM THE WALLET
Android<scheme>://taler-return?open=<talerUri>, explicit intent to the verified package that sent the payment
iOS<scheme>://taler-open?v=1&uriHash=<sha256>&uriHash=…. Only the hash leaves the wallet, because iOS cannot verify which app owns a scheme
Messengeropens only a payment it knows itself, jumps to the payment message, asks the exchange for the status right away. Never a value from the link
B-06 Rules for both sides
ENCODING
  1. Every value strictly percent-encoded: only A-Z a-z 0-9 - . _ ~ stay raw. Space is %20, never +.
  2. Repeated parameters instead of separators (uri=…&uri=…), order kept.
  3. Unknown parameters are ignored. For a non-repeatable parameter the first value wins.
  4. Names are never renamed. Extensions are additive only, so old and new versions keep working together.
SECURITY
  1. A pay-push/pay-pull URI is a bearer token: never logged, not in error messages or crash reports.
  2. The wallet returns only to <scheme>://taler-return and always rebuilds the query. Android checks package and signing certificate against AllowedCallers.
  3. The messenger accepts a return only for a known, unexpired, single-use correlationId and trusts no value in it: amount and status come from the exchange.
  4. Hints such as recipientHint or memberCount are untrusted and only displayed.
  5. Nothing in the messenger confirms a payment. Confirmation happens only in the wallet, by the user.

The full contract with error cases, data model and decision log lives with the code, in German: docs/taler-signal/ in the iOS wallet repository (link-protokoll.md, datenmodell.md, entscheidungen.md).

TITLETALER IN THE CHAT — LINK PROTOCOL MESSENGER ⇄ WALLET
DRAWNA. LENKENHOFF
CHECKED—
DATE2026-11-10
DRAWING NO.TLR-2026
REVISION1.0
SHEET2 / 4