TL;DR
Attach a single LangChain BaseCallbackHandler that calls Marturia’s receipt endpoint on every on_agent_action and on_chain_end. The handler signs the run ID, tool call, and observation so you can later prove exactly what the agent did.

flowchart LR Customer[Customer] --> LangChain[LangChain Agent] LangChain --> Callback[MarturiaCallbackHandler] Callback --> API[POST /api/marturia/v1/receipts] API --> Signed[Ed25519-signed receipt] Signed --> Verify[pip install marturia-verify]

Why a LangChain developer should care

Customers increasingly ask “prove the agent really said that.” Without an immutable record you are left with log files that can be edited. Marturia receipts give you a hash-chained, Ed25519-signed artifact that anyone can verify offline, satisfying both support tickets and compliance audits.

Implementing the callback handler

Create a handler that emits a receipt for every consequential step. The example below is fully runnable with LangChain 0.2+ and the official Marturia Python client.

from typing import Any, Dict, Optional
from uuid import UUID
from langchain_core.callbacks import BaseCallbackHandler
from langchain_core.agents import AgentAction, AgentFinish
import marturia

class MarturiaCallbackHandler(BaseCallbackHandler):
    def __init__(self, tenant_id: str, api_key: str):
        self.client = marturia.Client(tenant_id=tenant_id, api_key=api_key)

    def on_agent_action(
        self,
        action: AgentAction,
        *,
        run_id: UUID,
        parent_run_id: Optional[UUID] = None,
        **kwargs: Any,
    ) -> None:
        payload = {
            "agent_run_id": str(run_id),
            "action": action.tool,
            "tool_input": action.tool_input,
            "log": action.log,
        }
        self.client.create_receipt(
            event_type="agent_action",
            payload=payload,
            chain_id=str(parent_run_id) if parent_run_id else None,
        )

    def on_chain_end(
        self,
        outputs: Dict[str, Any],
        *,
        run_id: UUID,
        parent_run_id: Optional[UUID] = None,
        **kwargs: Any,
    ) -> None:
        payload = {
            "agent_run_id": str(run_id),
            "final_output": outputs,
        }
        self.client.create_receipt(
            event_type="chain_end",
            payload=payload,
            chain_id=str(parent_run_id) if parent_run_id else None,
        )

Attach it when you build the agent:

from langchain.agents import initialize_agent

handler = MarturiaCallbackHandler(tenant_id="acme", api_key=os.environ["MARTURIA_KEY"])
agent = initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, callbacks=[handler])

Receipt payload shape

Marturia expects a minimal but traceable object. The two fields that matter most are:

Everything else (timestamp, previous receipt hash, tenant signature) is added by the Marturia service.

Offline verification

Anyone can check receipts without contacting your servers:

pip install marturia-verify
marturia-verify --receipt receipt.json --public-key acme.pub

The tool walks the hash chain and validates the Ed25519 signature in a single command.

Common pitfalls

What this protects you from

Cryptographic receipts directly address the threat models described in the Marturia threat-models guide. They prevent an operator from retroactively changing an agent’s decision after a customer dispute and give regulators an immutable audit trail.

Related Marturia resources
- /docs/quickstart.html
- /docs/api.html
- /learn/lesson_07_threat_models.html