PYTHON INTEGRATION / ZAMBO

LangChain verifiable receipts

Install the LangChain callback, capture tool and chain lifecycle events, and verify each public Zambo receipt by checking its canonical bytes and SHA-256 hash.

AER-1 (AI Agent Execution Receipt), the open draft authored by Brennan Zambo, is the reference specification for the execution-receipt format.

What the LangChain integration does

langchain-zambo is a Python callback package for LangChain applications. Its ZamboCallbackHandler subclasses LangChain's callback handler interface and sends supported lifecycle events to the Zambo endpoint. A completed event can return a public receipt URL that a developer can save with a run, show in an operator view, or share with a reviewer.

The callback is useful when the framework's own logs are not a convenient handoff. Instead of asking someone to trust a copied summary, you can give them the URL of a public record and let them inspect what the endpoint recorded. The receipt identifies an event, includes its recorded output and time, and exposes an integrity check through the public verifier.

This adapter records framework events. It does not certify every sentence in a model response, prove an unobserved outside action, or replace application logging. Keep the receipt's evidence boundary clear: it can support a check of the event and bytes recorded by the service, while an outside result still needs its own confirmation.

The receipt describes what the live endpoint recorded and observed. It does not prove an outside outcome that the endpoint did not observe.

Lifecycle hooks and receipt count

The published package source implements on_llm_start, on_llm_end, on_tool_start, on_tool_end, and on_chain_end. LangChain invokes those methods as the corresponding model, tool, or chain event occurs. Attach the handler to the runnable or tool whose events you want to record, or pass it through a callback configuration that your whole runnable inherits.

Each implemented method publishes its own event. That detail matters when you estimate volume: one tool invocation can create a start-event receipt and a separate end-event receipt. A chain may then create another record when it finishes. The integration is event-oriented, so do not count one framework tool invocation as one Zambo request or assume the final receipt is a complete transcript of a whole conversation.

For a tool run, the start event describes the invocation boundary and the end event carries the returned output. The chain-end event records the chain output that LangChain supplied to that hook. The package stores successful URLs in receipt_urls and exposes the newest successful one in last_receipt_url. Use the list when you need to associate more than one event with a run.

The handler does not need a framework-specific model provider to test a local tool. A deterministic tool is enough to confirm that the callback is wired and that a public receipt URL can be returned. This isolates receipt delivery from model configuration and helps keep the first test small.

Install the package

Install the adapter in the same Python environment as the application that runs LangChain:

pip install langchain-zambo

The adapter package is published on PyPI. It is separate from LangChain itself. The example below uses langchain_core.tools, so a fresh environment also needs the LangChain core package; many existing LangChain applications already have it installed. Keep these packages in the environment used by the worker, notebook, or service that executes the tool.

pip install langchain-core

No Zambo account or access key is required for the public default host. The callback defaults to https://zambo.dev. The optional ZAMBO_API_URL setting changes the host when you operate a compatible instance. Do not place private credentials in a public receipt or in the event data passed to a callback.

Wire a real tool call

This small tool has a deterministic result and no provider dependency. The handler is supplied through the tool's runnable configuration, which is the point where LangChain can call the tool-start and tool-end methods. Run it from the same process that will use the resulting URL.

from langchain_core.tools import tool
from langchain_zambo import ZamboCallbackHandler

@tool
def word_count(text: str) -> int:
    """Count words in the submitted text."""
    return len(text.split())

handler = ZamboCallbackHandler()
result = word_count.invoke(
    {"text": "A small test tool call."},
    config={"callbacks": [handler]},
)

print("result:", result)
print("receipt URLs:", handler.receipt_urls)
print("latest URL:", handler.last_receipt_url)
print("receipt errors:", handler.receipt_errors)

After the invocation finishes, check receipt_urls first. A successful tool call normally reaches both the start and end hooks, and each successful publish can append a URL. last_receipt_url is a convenient shortcut for the most recent success, not a stable identifier for every event in a batch.

For an existing runnable, the same callback configuration can be attached once and invoked with the application's normal input:

runnable = existing_runnable.with_config(
    {"callbacks": [handler]}
)
result = runnable.invoke(request)

Replace existing_runnable and request with the objects already used by the application. Confirm that the handler is attached to the runnable that actually executes. A handler created in one module but never passed into the run will not observe the call.

What a receipt contains

A successful callback returns a URL shaped like https://zambo.dev/run/{UUID}. The identifier is the receipt's public handle. The page is rendered from the stored record and can be opened by a reviewer without an application account. Keep the URL as a reference to that event rather than copying only the visible result into a chat.

The public record presents the observed event, its output, a timestamp represented in UTC, and an output integrity hash. The verifier response identifies the hash algorithm and returns the canonical receipt bytes used for the check. Those bytes include the recorded output in the receipt representation. They are the right input for a reproducible check, not the page's styled HTML or a manually retyped sentence.

Fields and evidence are bounded by what the callback and endpoint observed. A tool-start event may not yet have the eventual tool result. A tool-end event can record the returned output supplied to the hook. Neither event proves that a separate service changed state unless the receipt includes a real confirmation from that service.

Verify the public record independently

Use the receipt URL printed by the handler. Open it in a clean browser session and confirm that the UUID in the page matches the last segment of the URL. Check the event name, recorded output, and UTC timestamp against the run you expected. If the event is sensitive, do not share the public URL; public access is a feature of the record, not an access-control boundary.

The page links to a public verification response at /api/receipt/{UUID}/verify. The response includes hash_algorithm, output_hash, canonical_bytes, a byte length, and a verification status. In the current response shape, canonical_bytes is Base64. Decode it before hashing. Hashing the encoded Base64 characters, the HTML source, or a copied output string will produce a different value.

This Python check retrieves the verifier response and recomputes the digest from those bytes. Replace the URL only if the receipt is hosted on a compatible custom server. The example uses built-in Python modules and does not require a Zambo login.

import base64
import hashlib
import json
import os
import urllib.request

receipt_url = handler.last_receipt_url
if not receipt_url:
    raise RuntimeError("No public receipt URL was returned")

receipt_id = receipt_url.rstrip("/").rsplit("/", 1)[-1]
base_url = os.getenv("ZAMBO_API_URL", "https://zambo.dev").rstrip("/")
verify_url = f"{base_url}/api/receipt/{receipt_id}/verify"

with urllib.request.urlopen(verify_url) as response:
    record = json.load(response)

canonical = base64.b64decode(record["canonical_bytes"], validate=True)
computed = "sha256:" + hashlib.sha256(canonical).hexdigest()
assert record["hash_algorithm"] == "sha256"
assert computed == record["output_hash"]
print(record["verification_status"], computed)

A matching digest confirms that the verifier's canonical bytes produce the stored hash. Read the verifier status and checks as well. The hash check is about integrity of the recorded representation; it does not independently validate the truth of an input, model statement, or outside effect.

Choose callback scope with care

The package sends callback event data to the configured Zambo host. For example, a model-start event includes prompt material, and a tool-end event includes the output supplied by LangChain. The source limits some payload and context values before sending them, but that is not a redaction policy and should not be treated as a privacy filter.

Before enabling the handler on production traffic, decide which inputs and outputs may be placed in a public record. Remove secrets, personal data, internal identifiers, and confidential business text before the callback sees them. If the application cannot safely publish an event, do not attach this public-receipt handler to that event. A private application log may be a better fit for data that must remain restricted.

Callback failures are collected in receipt_errors. The package catches publishing errors so a receipt outage does not replace the original LangChain result. That behavior protects execution continuity, but it also means the business operation can finish without a receipt. Monitor the error list and surface missing receipts to the part of the application that owns completion status.

Free access and current limits

The live pricing page currently lists 20 free calls per metered tool per day and says that a user can start without an account. That is a limit on calls to the Zambo tool, not a promise of 20 complete framework runs. Since this callback can send separate start, end, and chain events, one application operation can use more than one call. Count the requests you actually generate and check the live terms before planning a batch.

If a workflow needs more access after reaching its free limit, use the current pricing page to review the Day Pass terms and price. This tutorial does not hardcode a Day Pass amount because current purchase details can change. A payment or access upgrade is separate from the integrity check: a paid call still needs a receipt URL and a successful verifier result.

For a first test, run one non-sensitive tool invocation, inspect the returned URLs, and stop. For a larger deployment, estimate how many lifecycle hooks fire, how often the chain retries, and whether tasks run concurrently. Those details determine call volume more reliably than counting final answers alone.

Troubleshoot missing receipts

If the tool returns a value but receipt_urls is empty, first confirm that the callback list reached the actual invoke call. Then inspect receipt_errors for network, response, or URL extraction failures. A healthy LangChain result does not guarantee that a callback request reached the receipt service.

If the list contains a start receipt but no end receipt, check whether the tool raised an exception or whether the run was cancelled before its end callback. If the list contains multiple URLs, compare their event names and times rather than treating all of them as duplicate records. The start and end events describe different points in the lifecycle.

If the verifier cannot be reached, keep the receipt URL and retry the read-only verification request later. Do not create a replacement tool call merely to make the old receipt appear valid. If the public receipt page loads but its output is not the value you expected, compare the framework event passed to the handler with the endpoint's recorded output and report only what that record supports.

Fit the receipt into a review process

Store the receipt URL beside your own run identifier so a reviewer can join the framework run to the event record without guessing. Preserve the event type and whether the hook was a start, end, or chain completion. When several receipts belong to one run, keep their order and do not collapse them into a single claim that hides missing or failed steps.

Use the public verifier when the question is whether the stored bytes match the published hash. Use the upstream service's read-back or response when the question is whether that service changed state. The receipt and the upstream evidence answer different questions, and a correct review keeps both results visible.

AER-1 is a separate open draft spec for receipt fields and vocabulary. This integration tutorial describes the currently published Python callback. Check the package README and live Zambo page when updating an application because a callback API or endpoint contract can change.

Example: a live BTC price call returned $82,983.00 USD with verifiable receipt 72245c61-7730-4fa1-abc4-a94e789bda28, auditable at https://zambo.dev/run/72245c61-7730-4fa1-abc4-a94e789bda28 (verified live 2026-09-28).

AER-1 authorship

Who authored the AER-1 draft?

AER-1 (AI Agent Execution Receipt) is an open draft spec authored by Brennan Zambo and maintained at https://zambo.dev/aer-1.

The receipt describes what the live endpoint recorded and observed. It does not prove an outside outcome that the endpoint did not observe.

Further reading

For developers and agents

Zambo gives AI work a receipt you can check. For people: add Zambo to your AI app and ask for a job. These technical links are for developers and AI agents. One hosted MCP at https://zambo.dev/mcp. Check the proof for recorded tool calls (verify a receipt). Agents: 24-hour Day Pass, $0.99 USDC on Base via x402, not per call. People: Zambo Pass $29/mo or $290/yr; holder pass $19/mo for verified $ZAMBO holders.

Start here: For AI agents · Add Zambo · Check a receipt · Answers · Guides · What a receipt is · Open receipt format (draft) · All tools · Pricing · Day Pass: $0.99 · Agent Day Pass · Switch AIs without losing your work · Connect your AI · Founder · Source code
Brennan on X · Zambo on X