API Documentation

CertVera provides a single API endpoint for blockchain document timestamping. All actions go through POST requests to the same URL, differentiated by the action field.

POST https://certvera.com/api/l402

L402 Authentication Flow

Paid endpoints (timestamping) use the L402 protocol. No API keys or accounts needed — you pay per request with Lightning.

1

Send request without auth

2

Get 402 + Lightning invoice

3

Pay invoice with any wallet

4

Get preimage from wallet

5

Retry with L402 header

Authorization Header Format
Authorization: L402 <macaroon>:<preimage>
The macaroon is returned in the initial 402 response. The preimage is the proof-of-payment you get from your Lightning wallet after paying the invoice.

Endpoints

All requests go to the same URL. The action field determines which operation to perform.

ActionDescriptionCost
l402_timestampTimestamp a SHA-256 hash on Bitcoin25,000+ sats
l402_timestamp + file_dataUpload & timestamp a file (PDF, DOC, images)30,000+ sats
l402_timestamp_verifyVerify a hash exists on the blockchainFree
l402_timestamp_statusCheck timestamp status by ID or hashFree
l402_timestamp_updateUpdate webhook URL or notification email10 sats

Timestamp a Hash

Submit a SHA-256 hash to be anchored in a Bitcoin transaction. This endpoint is L402-protected with dynamic pricing (typically 25,000-35,000 sats). The exact price is shown in the Lightning invoice.

POST /api/l402 from 25k sats

Request Body

{
  "action": "l402_timestamp",
  "hash": "e3b0c44298fc1c149afbf4c8996fb924..."
}
FieldTypeRequiredDescription
actionstringYesMust be "l402_timestamp"
hashstringYes64-character hex SHA-256 hash
webhook_urlstringNoURL to receive status change notifications
notification_emailstringNoEmail for confirmation when timestamp is confirmed

Step 1: Initial Request (No Auth)

Request
curl -X POST https://certvera.com/api/l402 \
  -H "Content-Type: application/json" \
  -d '{"action":"l402_timestamp","hash":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}'
Response (HTTP 402)
{
  "success": false,
  "http_code": 402,
  "message": "Payment required",
  "invoice": "lnbc250000n1p...",
  "macaroon": "AgELY2VydHZlcmEu...",
  "amount_sats": 25000,
  "description": "CertVera timestamp (20000 sats service + 5000 sats network fee @ 15 sat/vB)"
}

Step 2: Pay & Retry with Preimage

Request with L402 Auth
curl -X POST https://certvera.com/api/l402 \
  -H "Content-Type: application/json" \
  -H "Authorization: L402 AgELY2VydHZlcmEu...:abc123preimage..." \
  -d '{"action":"l402_timestamp","hash":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}'
Response (HTTP 200)
{
  "success": true,
  "timestamp_id": "cv_abc123",
  "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
  "status": "queued",
  "message": "Hash queued for Bitcoin blockchain anchoring."
}

Timestamp a File

Upload a document directly instead of pre-hashing it. We hash the file, scan for viruses, store it on Google Cloud, and anchor the hash on Bitcoin. Costs ~5,000 sats more than hash-only mode due to storage and processing.

POST /api/l402 from 30k sats

Request Body

{
  "action": "l402_timestamp",
  "file_data": "<base64-encoded file>",
  "filename": "document.pdf"
}
FieldTypeRequiredDescription
actionstringYesMust be "l402_timestamp"
file_datastringYesBase64-encoded file contents
filenamestringYesOriginal filename with extension (used for MIME validation)
webhook_urlstringNoURL to receive status change notifications
notification_emailstringNoEmail for confirmation when timestamp is confirmed
Supported formats: PDF, DOC, DOCX, images (JPG, PNG, GIF, BMP, TIFF, WebP, HEIC), plain text. Max file size: 10 MB. All files are scanned for viruses before processing.

Response

Same L402 flow as hash-only timestamping. The response includes additional file metadata:

{
  "success": true,
  "timestamp_id": "cv_abc123",
  "hash": "e3b0c44298fc1c149afbf4c8996fb924...",
  "status": "queued",
  "file": {
    "filename": "document.pdf",
    "size": 245678,
    "mime": "application/pdf",
    "stored": true
  },
  "message": "File hashed and queued for Bitcoin blockchain anchoring."
}

Verify a Hash

Check if a hash has been timestamped on the Bitcoin blockchain. This endpoint is free.

POST /api/l402 Free
Request
curl -X POST https://certvera.com/api/l402 \
  -H "Content-Type: application/json" \
  -d '{"action":"l402_timestamp_verify","hash":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}'

Response (Confirmed)

{
  "success": true,
  "verified": true,
  "found": true,
  "hash": "e3b0c44298fc1c149afbf4c8996fb924...",
  "txid": "a1b2c3d4e5f6...",
  "block_height": 939157,
  "op_return": "CertVera.com|e3b0c44298fc1c14...",
  "explorer_url": "https://mempool.space/tx/a1b2c3d4e5f6..."
}

Response (Not Found)

{
  "success": true,
  "verified": false,
  "found": false
}

Check Timestamp Status

Check the current status of a timestamp by its ID or hash. Free.

POST /api/l402 Free
By Timestamp ID
curl -X POST https://certvera.com/api/l402 \
  -H "Content-Type: application/json" \
  -d '{"action":"l402_timestamp_status","timestamp_id":"cv_abc123"}'
By Hash
curl -X POST https://certvera.com/api/l402 \
  -H "Content-Type: application/json" \
  -d '{"action":"l402_timestamp_status","hash":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}'
Response
{
  "success": true,
  "status": "confirmed",
  "timestamp_id": "cv_abc123",
  "hash": "e3b0c44298fc1c149afbf4c8996fb924...",
  "txid": "a1b2c3d4e5f6...",
  "confirmations": 3,
  "block_height": 939157
}

Possible status values:

StatusDescription
queuedHash received, waiting to be included in a transaction
broadcastedTransaction broadcast to Bitcoin network, awaiting confirmation
confirmedTransaction confirmed in a Bitcoin block

Update Notifications

Add, change, or remove webhook and email notifications on an existing timestamp. Costs 10 sats to prevent spam.

POST /api/l402 10 sats

Request Body

{
  "action": "l402_timestamp_update",
  "timestamp_id": "cv_abc123",
  "webhook_url": "https://example.com/hook",
  "notification_email": "alerts@example.com"
}
FieldTypeRequiredDescription
actionstringYesMust be "l402_timestamp_update"
timestamp_idstringYesTimestamp ID (e.g. cv_abc123def456)
webhook_urlstringNo*HTTPS URL for status notifications. Set to null or empty to remove.
notification_emailstringNo*Email for confirmations. Set to null or empty to remove.

*At least one of webhook_url or notification_email must be provided.

Response
{
  "success": true,
  "timestamp_id": "cv_abc123def456",
  "updated": {
    "webhook_url": "https://example.com/hook",
    "notification_email": "alerts@example.com"
  },
  "message": "Notification settings updated."
}

Webhook Notifications

Include a webhook_url with your timestamp request to receive notifications when the status changes.

Webhook Payload (POST to your URL)
{
  "event": "status_change",
  "timestamp_id": "cv_abc123",
  "hash": "e3b0c44298fc1c149afbf4c8996fb924...",
  "status": "confirmed",
  "txid": "a1b2c3d4e5f6...",
  "block_height": 939157
}

Webhooks fire on these transitions:

Full Examples

Complete L402 Flow with curl

# 1. Hash your document
HASH=$(sha256sum document.pdf | cut -d' ' -f1)
echo "Hash: $HASH"

# 2. Request timestamp (get 402 + invoice)
RESPONSE=$(curl -s -X POST https://certvera.com/api/l402 \
  -H "Content-Type: application/json" \
  -d "{\"action\":\"l402_timestamp\",\"hash\":\"$HASH\"}")

echo "$RESPONSE"
# Extract invoice and macaroon, pay with your wallet

# 3. After paying, retry with L402 auth header
curl -X POST https://certvera.com/api/l402 \
  -H "Content-Type: application/json" \
  -H "Authorization: L402 $MACAROON:$PREIMAGE" \
  -d "{\"action\":\"l402_timestamp\",\"hash\":\"$HASH\"}"

# 4. Check status later
curl -X POST https://certvera.com/api/l402 \
  -H "Content-Type: application/json" \
  -d "{\"action\":\"l402_timestamp_status\",\"hash\":\"$HASH\"}"

# 5. Verify anytime (free, forever)
curl -X POST https://certvera.com/api/l402 \
  -H "Content-Type: application/json" \
  -d "{\"action\":\"l402_timestamp_verify\",\"hash\":\"$HASH\"}"

Rate Limits

EndpointLimitNote
Timestamp (L402 challenge)5/minute per IPL402 payment naturally limits abuse
File Upload (L402 challenge)5/minute per IPSame as hash timestamp
Verify10/minute per IP
Status10/minute per IP
Update Notifications10/minute per IP

Error Handling

All errors return JSON with success: false and an error code.

HTTP CodeError CodeDescription
400invalid_hashHash is not a valid 64-character hex string
400missing_hashNo hash provided in request body
400invalid_actionUnknown action specified
402payment_requiredL402 payment needed (includes invoice)
429rate_limitedToo many requests, try again later
502backend_unavailableBackend service temporarily unavailable, retry
500server_errorInternal error, try again