Skip to content

Signing by link ​

A tokenised link, the document on screen, and a button. No provider involved.

This exists because Nafath cannot be exercised without a staging deployment inside Saudi Arabia, and because not every document needs a government identity check. It is a weaker attestation than Nafath and is recorded as such everywhere it appears.

What it proves, exactly ​

At accepted_at, a request from ip_address submitted an acceptance of the document whose bytes digest to document_digest, typing typed_name.

What it does not prove ​

Who that was. Nothing authenticated the person. The link is a bearer credential, so anyone it reached could press the button, and the typed name is the presser's own claim about themselves.

Why this is not a SigningAttempt

An attempt carries a provider's verified claims about an identity. A click carries none, so storing one as an attempt would have meant inventing claims to satisfy the completion constraint — and would have made every query that asks "is this signed" unable to tell a Nafath authentication from a click.

Signing::Acceptance is a separate record for that reason.

The assurance travels with the claim ​

Envelope#assurance is one of three values, and #assurance_statement is the one sentence the model composes so no screen can print "Signed" without printing what that word is worth:

AssuranceThe sentence
provider_verifiedSigned — the signatory was authenticated by provider, and the document they approved is the one kept here
click_throughAccepted online — the document kept here is the one that was on screen, and the person who accepted it was not identity-verified
unsignedNot signed

A screen that composed its own label would sooner or later print a bare "Signed" for a click, which is the single most misleading thing this record can say. Invariant 18 walks every value in the vocabulary and fails if one has no statement, if two read alike, or if a non-provider assurance claims an identity check.

GET /sign/:token. The token is a signed, expiring reference — nothing is stored, so there is no bearer credential in the database to leak.

Its digest covers two things, and both matter:

  • the status — withdrawing a contract kills every link outstanding for it
  • the document digest — a link can only ever open the document it was issued for

It lasts 30 days: long, because a merchant agreement waits on somebody who has a business to run, and bounded, because a link with no end date is a permanent bearer credential.

There is no revoke-without-withdrawing

If a link leaks, the remedy is to withdraw the contract and prepare another. That is the right remedy anyway — the alternative leaves the same document signable by whoever received the link.

One refusal for every reason ​

An expired link, a withdrawn agreement and a token that never existed all return the same 410. Telling them apart would let anyone holding a stale link learn which merchants have live contracts.

The page ​

No navigation, no sign-in link, no operator chrome. The signatory is a merchant's authorized signatory, not a team member, and nothing on the page leads anywhere except to reading the document and deciding.

The document is served from the archive, not re-rendered. Re-rendering would produce a document from today's values, which is not necessarily the bytes the digest covers — and the digest is what the acceptance points at. The bytes are checked against it before being served; a copy that has diverged says so instead of rendering.

It is framed, under default-src 'none', so it can execute and fetch nothing.

The browser they have, not the one we would prefer ​

The operator application requires a modern browser. This page deliberately does not.

A signatory did not choose our browser policy and cannot be told to upgrade — iOS Safari 15 and Chrome 100 both sit below that floor, and both were served a bare 406 on the page where somebody signs a contract, with nothing saying why. Refusing our own staff a stale browser is a policy; refusing a customer's signatory is a lost contract.

Nothing here needs what that floor buys: the document is framed HTML with inline styles, and the form is an ordinary POST that works with Turbo absent.

What the signatory is told ​

Signing here records the date, your IP address, and a fingerprint of this exact document. It does not verify your identity — this link is the only thing that got you here, so keep it to yourself and do not forward it.

Said to the person pressing the button, not only recorded in a database.

Preparing one ​

Prepare a contract on the merchant's page, needing contract.prepare. Three things happen together:

  1. An envelope is built from the version in force — never a draft, whose wording can still change
  2. It is presented: rendered, digested, and the bytes archived
  3. Only then does a link exist

Two refusals, both deliberate

No authorized signatory, no contract. The signatory is the party the registry records as authorized to sign — not whichever owner comes first. A contract naming somebody the registry never authorised says something untrue on its first page.

Two applicable templates is a question. Choosing for the operator would bind a merchant to whichever row the database returned first.

The standard pricing offer is supplied by Entities::MerchantAgreement::STANDARD_FEES. A bespoke schedule is passed as extra_values and overrides it.

Permissions ​

contract.viewRead the contracts prepared for a merchant, what each bound, and how it was signed
contract.preparePrepare one and issue its link. Anyone holding that link can accept the document, so this is the authority to bind a merchant

Reading a contract does not show you its link. A contract you may not see is 404, the same answer as one belonging to another account — otherwise the status code becomes a way to probe which contracts exist.

What is recorded ​

contract.prepared, contract.accepted and contract.voided reach the merchant's activity history.

The acceptance event has no actor: the person who accepted is not a Verity user, and naming one would attribute the signature to whoever happened to prepare the contract.