Receives TLSNotary verifier webhooks and creates EAS attestations on Sepolia testnet.
- Node.js >= 18
- A Sepolia wallet funded with testnet ETH (for gas)
cd packages/eas-webhook
npm installGenerate a testnet wallet (or use an existing one):
npm run create-walletThis prints an address and private key. Fund it with Sepolia ETH from a faucet (e.g. sepoliafaucet.com), then export the key:
export PRIVATE_KEY=0x...Warning:
create-walletis for testing only. Do not use the generated wallet for mainnet or real funds.
A script is provided to register the schema on Sepolia automatically:
PRIVATE_KEY=0x... npm run register-schemaThis registers the following schema (non-revocable, no resolver):
string artistName, string serverName, string verifierUrl, bytes32 transcriptHash, uint64 timestamp
The script prints the schema UID on success:
Schema registered successfully!
Schema UID: 0x...
Export it:
export EAS_SCHEMA_UID=0x...
Export the UID:
export EAS_SCHEMA_UID=0x...npm run devThe server starts on port 3001 by default.
| Variable | Required | Default | Description |
|---|---|---|---|
PRIVATE_KEY |
Yes | — | Sepolia wallet private key for signing attestation transactions |
EAS_SCHEMA_UID |
Yes | — | Schema UID from step 2 above |
PORT |
No | 3001 |
HTTP server port |
SEPOLIA_RPC |
No | https://ethereum-sepolia-rpc.publicnode.com |
Sepolia RPC endpoint |
VERIFIER_URL |
No | https://demo.tlsnotary.org |
Verifier URL stored in attestations |
| Method | Path | Description |
|---|---|---|
GET |
/health |
Health check — returns "ok" |
POST |
/register |
Register a correlation ID with an ETH address and wallet signature |
POST |
/webhook |
Receives verifier webhook payload, creates EAS attestation |
GET |
/attestation/:id |
Poll attestation status by correlation ID |
{
"correlationId": "uuid",
"ethAddress": "0x...",
"signature": "0x...",
"message": "Attest my Spotify data to 0x..."
}The signature is verified against the provided address using ethers.verifyMessage.
Receives the verifier webhook payload automatically. The correlationId is read from session.data.correlationId. The artist name is extracted from the RECV/BODY handler result.
{
"status": "complete",
"artistName": "Radiohead",
"serverName": "api.spotify.com",
"transcriptHash": "0x3a7f...9b2c",
"txHash": "0xabc1...def2",
"attestationUid": "0x..."
}Status values: pending, attesting, complete, error.
The service is included in the demo's docker-compose.yml:
# 1. Register schema (one-time, from packages/eas-webhook)
cd packages/eas-webhook && npm install
PRIVATE_KEY=0x... npm run register-schema
# Note the schema UID from the output
# 2. Start all services
cd packages/demo
export PRIVATE_KEY=0x...
export EAS_SCHEMA_UID=0x... # from step 1
docker compose up --buildThe nginx proxy routes /api/* to this service. The verifier is configured (via verifier-config.yaml) to send api.spotify.com webhooks to http://eas-webhook:3001/webhook.
Each attestation contains:
| Field | Type | Description |
|---|---|---|
artistName |
string |
The proven top artist from Spotify |
serverName |
string |
TLS server hostname (e.g. api.spotify.com) |
verifierUrl |
string |
Verifier that performed the verification |
transcriptHash |
bytes32 |
keccak256(sent_transcript || recv_transcript) — links to the TLS proof |
timestamp |
uint64 |
Unix timestamp of attestation creation |
The attestation recipient is the user's ETH address (verified via wallet signature). The attester is the relayer wallet.
The EAS webhook service links a user's wallet address to a TLSNotary proof using a correlation ID:
Frontend EAS Webhook Server Verifier
│ │ │
│ 1. Connect wallet, sign msg │ │
│ │ │
│ 2. POST /register │ │
│ {correlationId, address, │ │
│ signature} │ │
│ │ │
│ Verifies signature, │ │
│ stores: uuid → address │ │
│ │ │
│ 3. execCode(plugin, │ │
│ {sessionData: │ │
│ {correlationId}}) │ │
│ │ │ │
│ └─── prove() ────────┼───────────────────────────→│
│ │ │
│ │ 4. POST /webhook │
│ │ {session: │
│ │ {correlationId}, │
│ │ results, transcript} │
│ │ │
│ │ 5. Looks up uuid → address │
│ │ Submits EAS attestation │
│ │ with recipient = address│
│ │ │
│ 6. GET /attestation/:id │ │
│ ← {status, txHash, uid} │ │
- Frontend generates a random
correlationId(UUID) and registers it with the EAS server alongside the user's wallet address + a wallet signature proving ownership. - EAS server verifies the signature via
ethers.verifyMessage, then stores the mappingcorrelationId → ethAddress. - Frontend passes the same
correlationIdintoexecCode()assessionData. The extension threads this through SessionManager →prove()→ verifier. - Verifier completes the MPC-TLS proof and fires a webhook containing the
correlationIdinsession.data, along with the proven results and transcript. - EAS server receives the webhook, looks up the
correlationIdto find the registered wallet address, and submits an EAS attestation on Sepolia with that address as the recipient. - Frontend polls
GET /attestation/:iduntil the attestation transaction is confirmed.
A webhook buffer handles race conditions where the webhook arrives before the registration completes (within a 30-second window).
This is a demo implementation. The following limitations apply if adapting for production:
A user can generate multiple EAS attestations from the same TLSNotary proof (or the same underlying data). Each prove() call creates a new correlationId, so the same Spotify account can produce unlimited attestations.
Possible mitigations:
- Hash-based deduplication on-chain: Include a hash of the user's proven identity (e.g.,
keccak256(spotifyUsername)) in the attestation schema. A resolver contract can check for existing attestations with the same identity hash and reject duplicates. - Server-side deduplication: The verifier or EAS webhook server maintains a set of previously attested identities (e.g., Spotify usernames) in a database and rejects duplicate webhook payloads.
Registrations, attestations, and buffered webhooks are stored in memory. They are lost on server restart and limited to 1000 entries with a 1-hour TTL. A production deployment should use persistent storage.
All attestations are signed by a single relayer wallet. The private key must be kept secure. Consider using a hardware security module (HSM) or multisig for production.