Cookbook
Verify an incorporator receipt
Confirm Matter's certified copy from the secretary of state — file ID, accepted timestamp, original PDF.
Last updated
Confirm Matter's certified copy from the secretary of state — file ID, accepted timestamp, original PDF.
Trigger
A counterparty is asking for proof of incorporation independent of you.
Call sequence
1. Retrieve the receipt
GET /v1/entities/{id}/incorporator_receiptIdempotency
Read-only.
Errors
| Status | Code | Description |
|---|---|---|
404 | receipt_not_ready | Entity not yet formed. |
Outcome
You hold a tamper-evident record, verifiable without trusting Matter's API at the moment of verification, that:
- The natural-person founder named at
signer_stakeholder_idsigned the Certificate of Incorporation onfiled_at. - The signature was captured under the recorded
signer_basis(wet_signature,esra_consent, orueta_electronic_agent) with the correspondingconsent_recordoragent_authorityblock populated. - The receipt body (entity ID, jurisdiction, signer, filing timestamp, consent metadata) was not modified after signing.
The verification is fully offline once the receipt and key are downloaded.
Matter does not sign as incorporator: the founder always does. The
platform-witnessed key over the canonical body proves the signature event
was captured intact; the signer_basis field tells you which legal
scaffolding the signature stands on.
Legacy receipts. Receipts issued under the previous
Matter-as-incorporator model still verify with the same canonical-body +
Ed25519 scheme — the deprecated fields (incorporator_identity,
transferred_at, transferred_to_stakeholder_id) are populated instead of
signer_stakeholder_id. Verifiers should branch on the presence of
signer_stakeholder_id to pick the right code path.
Pre-flight
Receipts are issued only after the Certificate of Incorporation is accepted
by the Secretary of State. Drafts and rejected entities have no receipt.
Confirm via entity.incorporator_receipt_id != null.
Test-mode receipts (livemode: false) are signed with a distinct key —
https://mattermode.com/keys/incorporator-test.pub. Live receipts use
https://mattermode.com/keys/incorporator.pub. Test-mode receipts cannot
be laundered into live mode because the keys differ.
Pin the public key fingerprint in your code or CI. Matter rotates the key on a published cadence (every 24 months) — the rotation announces in the changelog with overlap windows so existing receipts remain verifiable.
Offline verification
The full canonical-body + Ed25519 verification recipe (with a worked TypeScript implementation) is documented at Incorporator protocol — Verifying offline. The Python implementation below mirrors that recipe one-to-one and is the reference for non-Node verifiers.
import json
import os
import requests
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
from cryptography.exceptions import InvalidSignature
def canonical_json(value) -> str:
"""RFC 8785 — JSON Canonicalization Scheme. Use the `rfc8785` library
in production; the inline form below is illustrative."""
return json.dumps(
value,
sort_keys=True,
separators=(",", ":"),
ensure_ascii=False,
)
def fetch_public_key(livemode: bool) -> bytes:
url = (
"https://mattermode.com/keys/incorporator.pub"
if livemode
else "https://mattermode.com/keys/incorporator-test.pub"
)
res = requests.get(url, timeout=5)
res.raise_for_status()
return res.content
def validate_signer_scaffolding(receipt: dict) -> None:
"""New receipts carry signer_stakeholder_id + signer_basis; legacy receipts
use the deprecated incorporator_identity field. Branch and assert the
appropriate block is present."""
if receipt.get("signer_stakeholder_id"):
basis = receipt.get("signer_basis")
if basis == "esra_consent" and not receipt.get("consent_record"):
raise ValueError("esra_consent receipt missing consent_record")
if basis == "ueta_electronic_agent" and not receipt.get("agent_authority"):
raise ValueError("ueta_electronic_agent receipt missing agent_authority")
elif not receipt.get("incorporator_identity"):
raise ValueError(
"Receipt has neither signer_stakeholder_id nor legacy incorporator_identity"
)
def verify_receipt(receipt: dict) -> bool:
# 1. Branch on signer_basis (or legacy fields) and assert the expected block is present.
validate_signer_scaffolding(receipt)
# 2. Recompute the canonical body. The signature itself must be excluded.
rest = {k: v for k, v in receipt.items() if k != "signature"}
canonical = canonical_json(rest).encode("utf-8")
# 3. Verify the Ed25519 signature against the platform-witnessed public key.
pem = fetch_public_key(receipt["livemode"])
public_key = serialization.load_pem_public_key(pem)
if not isinstance(public_key, Ed25519PublicKey):
raise ValueError("Expected Ed25519 public key")
signature = bytes.fromhex(receipt["signature"])
try:
public_key.verify(signature, canonical)
return True
except InvalidSignature:
return False
# Usage
res = requests.get(
"https://api.mattermode.com/v1/incorporator_receipts/rcp_8mY3pQrL",
headers={
"Authorization": f"Bearer {os.environ['MATTER_KEY']}",
"Matter-Version": "2026-05-01",
},
)
res.raise_for_status()
receipt = res.json()
if verify_receipt(receipt):
print("Receipt verified")
print(
f"Founder: {receipt['signer_stakeholder_id']} "
f"signed via {receipt['signer_basis']}"
)
else:
print("Receipt invalid")For the Rail 2 variant (agent under UETA §14), signer_basis is
ueta_electronic_agent, consent_record is null, and agent_authority
carries the token_id, principal_human_id, standing_policy_id, and the
list of acknowledgements in force at signing time. See
form an entity with an agent for
the shape of that receipt.
Error recovery
| Failure | Cause | Remediation |
|---|---|---|
404 Not Found receipt_not_found | Wrong receipt ID, or entity hasn't reached registered yet. | Inspect Entity.incorporator_receipt_id. New entities take ~30 minutes (live) or seconds (test) to issue receipts. |
Local InvalidSignature over canonical body | The receipt was modified after signing, or canonicalization differs. | Re-canonicalize per RFC 8785 (use a tested library) and re-fetch from Matter. If the live API copy still fails verification under a known-good public key, contact support@mattermode.com immediately — this would indicate a system fault. |
Local InvalidSignature (key mismatch) | Wrong public key (test vs live mismatch), or key rotated past your cached version. | Refetch the public key. Check signing_key_fingerprint against the published key registry at https://mattermode.com/keys/registry.json. |
Local signer_basis block missing | A new receipt with signer_basis: "esra_consent" is missing consent_record, or signer_basis: "ueta_electronic_agent" is missing agent_authority. | Re-fetch the receipt; if the issue persists, contact support. New receipts must always populate the matching block. |
| Connection error fetching public key | Matter's key server unreachable. | Use a pinned local copy. Production verifiers should never depend on a live fetch — bake the key into the artifact. |
403 Forbidden livemode_mismatch | Your sk_test_… key trying to read a livemode: true receipt. | Test keys can only read test receipts; live keys can only read live receipts. Switch keys. |
Full taxonomy: errors.
Variations
For CI verification (e.g. asserting M&A targets' formations are valid), pin the key as a checked-in file:
import { readFileSync } from "node:fs";
async function fetchPublicKey(livemode: boolean): Promise<string> {
// Pinned to a known fingerprint; bump on key rotation.
const path = livemode
? "./keys/incorporator-2026q1.pub"
: "./keys/incorporator-test-2026q1.pub";
return readFileSync(path, "utf8");
}The fingerprint is published at
https://mattermode.com/keys/registry.json with rotation history.
Surface who signed and on what legal basis. For the agent variant, walk the
agent_authority block to recover the human principal and the standing policy
that authorized the signature.
console.log(`Signer: ${receipt.signer_stakeholder_id}`);
console.log(`Basis: ${receipt.signer_basis}`);
if (receipt.signer_basis === "esra_consent" && receipt.consent_record) {
console.log(`ESRA disclosure: ${receipt.consent_record.esra_disclosure_version}`);
console.log(`Accepted at: ${new Date(receipt.consent_record.accepted_at * 1000).toISOString()}`);
}
if (receipt.signer_basis === "ueta_electronic_agent" && receipt.agent_authority) {
const a = receipt.agent_authority;
console.log(`Token: ${a.token_id}`);
console.log(`Human: ${a.principal_human_id}`);
console.log(`Policy: ${a.standing_policy_id ?? "(per-call)"}`);
console.log(`Slugs: ${a.acknowledgements.length} affirmed`);
}For legacy receipts (signer_stakeholder_id absent) read
incorporator_identity and transferred_to_stakeholder_id instead.
Fetch the full key history (with rotation timestamps) for any-time verification:
curl https://mattermode.com/keys/registry.jsonResponse shape:
{
"current": {"id": "key_2026q1", "fingerprint": "…", "rotated_at": null},
"previous": [
{"id": "key_2024q1", "fingerprint": "…",
"rotated_at": "2026-01-15T00:00:00Z",
"valid_for": "<2026-01-15"}
],
"test_current": {"id": "test_key_2026q1", "fingerprint": "…"}
}Pick the key whose valid_for window contains the receipt's filed_at.
Related
GET /v1/incorporator_receipts/{id}— primitive- Incorporator protocol — full chain semantics
- Form a company — how receipts are created
- Versioning — key rotation cadence