Webhook → evidence

VLNotary

Permanent parsed JSON fields or verbatim text, with permissionless contract subscriptions.

What VLNotary is for.

Permanent parsed JSON fields or verbatim text, with permissionless contract subscriptions.

Only the configured relay can emit evidence and deliver it. Any contract may register its own typed callback for an exact PTR service and event; registration is routing, not authorization.

How it fits together.

  1. The relay validates a webhook's network identity and publishes parsed JSON fields or plain text.
  2. A subscribing contract registers an event, PTR service, callback, and field mapping.
  3. VLNotary delivers the selected values once and exposes delivery context during the callback.

Declared public interface.

These are the public and external methods declared directly by VLNotary. Inherited token, ownership, and upgrade methods follow their respective standards.

setRelay(address newRelay)Transaction · onlyOwner

Updates the named protocol configuration value or permission.

transferOwnership(address newOwner)Transaction · onlyOwner

Executes the transfer ownership operation.

setCertifier(address certifier, bool enabled)Transaction · onlyOwner

Updates the named protocol configuration value or permission.

deliveryContext() returns (bytes32 receiptId, string memory service, string memory eventType, string memory entity)Read

Returns the active notary delivery metadata while a callback is executing.

registerSubscription(string calldata service, string calldata eventName, bytes4 callbackSelector, string[] calldata parameterKeys, ParameterType[] calldata parameterTypes) returns (uint256 subscriptionId)Transaction

Registers the supplied account, relationship, or event subscription.

removeSubscription(uint256 subscriptionId)Transaction

Removes or releases the specified record, permission, or held value.

subscription(uint256 subscriptionId) returns (address subscriber, bytes32 serviceHash, bytes32 eventHash, bytes4 callbackSelector, bool active, string[] memory parameterKeys, ParameterType[] memory parameterTypes)Read

Reads the requested contract state or calculated result without changing state.

subscriptionIds(string calldata service, string calldata eventName) returns (uint256[] memory)Read

Reads the requested contract state or calculated result without changing state.

notarizeWebhook(bytes32 receiptId, string memory eventName, string memory callerIp, string memory service, string memory entity, bool isJson, string memory text, string[] memory keys, string[] memory values)Transaction · onlyRelay

Executes the notarize webhook operation.

deliverWebhook(bytes32 receiptId, string calldata eventName, string calldata callerIp, string calldata service, string calldata entity, bool isJson, string calldata text, string[] calldata keys, string[] calldata values, uint256 subscriptionId) returns (bool success)Transaction · onlyRelay

Executes the deliver webhook operation.

certifyContractEvent(bytes32 receiptId, string calldata eventName, string calldata service, string calldata entity, string[] calldata keys, string[] calldata values, uint256 subscriptionId) returns (bool success)Transaction

Executes the certify contract event operation.

Connect with ethers v6.

import { Contract, JsonRpcProvider, Wallet, encodeBytes32String, parseUnits } from "ethers";

const provider = new JsonRpcProvider(process.env.RPC_URL);
const signer = new Wallet(process.env.SIGNER_PRIVATE_KEY, provider);
const at = (address, abi, writable = false) =>
  new Contract(address, abi, writable ? signer : provider);

const notary = at(NOTARY, [
  "event WebhookNotarized(bytes32 indexed receiptId,string eventName,string callerIp,string service,string entity,bool isJson,string text,string[] keys,string[] values)"
]);
notary.on("WebhookNotarized", (receiptId, eventName, callerIp, service, entity, isJson, text, keys, values) => {
  const payload = isJson ? Object.fromEntries(keys.map((key, index) => [key, values[index]])) : text;
  if (entity === "priceedge.me") console.log(receiptId, service, payload);
});

From provider request to contract callback.

Notary is the authorized off-chain relay for external evidence. A provider posts to an HTTPS endpoint shaped as <entity>.notary.vowlabs.dev/<event-name>. The listener derives the entity from the hostname and the event type from the path; it obtains the caller address through the trusted reverse proxy and records its reverse-DNS name as the asserted service.

  1. Validate and normalize. JSON is parsed, constrained to a fail-closed allowlist, stripped of the entity’s additional PII denylist, flattened into deterministic sorted key/value pairs, and capped at the configured field limit. Non-JSON bodies are preserved as verbatim text.
  2. Make evidence idempotent. The entity, event name, and raw body produce a deterministic receipt ID. A receipt already recorded on-chain is acknowledged without publishing a second event.
  3. Notarize on-chain. The relay wallet—whose address must equal VLNotary.relay()—submits the privacy-filtered evidence. The contract permanently records the receipt and its context: event, caller IP, PTR service, entity, and either structured fields or text.
  4. Deliver selected fields. The listener finds active subscriptions matching that exact service and event, then submits a separate delivery transaction for each one. VLNotary rechecks the receipt, converts requested values to the subscriber’s declared ABI types, and invokes its callback once. A malformed value or reverting callback produces a failed delivery record without deleting the evidence or stopping other deliveries.
  5. Authorize in the consumer. A subscription is only routing. The receiving contract must verify the expected chain, Notary address, entity, service, event, schema, customer/account binding, and replay policy before it mints, settles, or otherwise acts.

Notary also offers DNS-signed, entity-specific PII-policy initialization and periodically checks completed domain registrations with a DNSSEC-validating resolver. Those operations are off-chain controls; only the filtered evidence and delivery results are committed to VLNotary.

Authorize the entity policy with DNS and a signature.

The initializer is an HTTPS POST to an ordinary event URL with {"command":"PII","fields":[...]}. The exact raw JSON bytes must be signed with the entity’s Ed25519 private key and sent in X-VL-Notary-Entity-Signature. The relay derives the entity from the hostname and resolves its public key from DNS.

pnpm -C VowLabs/Offchain notary:init -- keygen \
  --private-key /secure/priceedge/vlnotary-ed25519.pem

# Publish the public key printed by keygen:
# _vlnotary.priceedge.me TXT "v=vlnotary1; k=ed25519; p=<base64-SPKI-DER>"

pnpm -C VowLabs/Offchain notary:init -- initialize \
  --url https://priceedge.me.notary.vowlabs.dev/finance:bank:deposit \
  --private-key /secure/priceedge/vlnotary-ed25519.pem \
  --fields ssn,sender_name,accountNumber,routingNumber,description,summary,addenda

The caller must control the private key whose public half is published at _vlnotary.priceedge.me; the hostname and key must refer to the same entity. Keep the private key out of clients and source control. Use DNSSEC and a validating resolver where possible. The initializer is stored locally, not emitted on-chain.

Use the address for the chain you selected.

Read the deployment manifest or resolve the configured registry entry for the target network. Test on a local or test network before using a privileged signer. See the contract source and contract index for the full interface.