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.
L402 Authentication Flow
Paid endpoints (timestamping) use the L402 protocol. No API keys or accounts needed — you pay per request with Lightning.
Send request without auth
Get 402 + Lightning invoice
Pay invoice with any wallet
Get preimage from wallet
Retry with L402 header
Authorization: L402 <macaroon>:<preimage>
Endpoints
All requests go to the same URL. The action field determines which operation to perform.
| Action | Description | Cost |
|---|---|---|
l402_timestamp | Timestamp a SHA-256 hash on Bitcoin | 25,000+ sats |
l402_timestamp + file_data | Upload & timestamp a file (PDF, DOC, images) | 30,000+ sats |
l402_timestamp_verify | Verify a hash exists on the blockchain | Free |
l402_timestamp_status | Check timestamp status by ID or hash | Free |
l402_timestamp_update | Update webhook URL or notification email | 10 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.
Request Body
{
"action": "l402_timestamp",
"hash": "e3b0c44298fc1c149afbf4c8996fb924..."
}
| Field | Type | Required | Description |
|---|---|---|---|
action | string | Yes | Must be "l402_timestamp" |
hash | string | Yes | 64-character hex SHA-256 hash |
webhook_url | string | No | URL to receive status change notifications |
notification_email | string | No | Email for confirmation when timestamp is confirmed |
Step 1: Initial Request (No Auth)
curl -X POST https://certvera.com/api/l402 \
-H "Content-Type: application/json" \
-d '{"action":"l402_timestamp","hash":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}'
{
"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
curl -X POST https://certvera.com/api/l402 \
-H "Content-Type: application/json" \
-H "Authorization: L402 AgELY2VydHZlcmEu...:abc123preimage..." \
-d '{"action":"l402_timestamp","hash":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}'
{
"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.
Request Body
{
"action": "l402_timestamp",
"file_data": "<base64-encoded file>",
"filename": "document.pdf"
}
| Field | Type | Required | Description |
|---|---|---|---|
action | string | Yes | Must be "l402_timestamp" |
file_data | string | Yes | Base64-encoded file contents |
filename | string | Yes | Original filename with extension (used for MIME validation) |
webhook_url | string | No | URL to receive status change notifications |
notification_email | string | No | Email for confirmation when timestamp is confirmed |
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.
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.
curl -X POST https://certvera.com/api/l402 \
-H "Content-Type: application/json" \
-d '{"action":"l402_timestamp_status","timestamp_id":"cv_abc123"}'
curl -X POST https://certvera.com/api/l402 \
-H "Content-Type: application/json" \
-d '{"action":"l402_timestamp_status","hash":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}'
{
"success": true,
"status": "confirmed",
"timestamp_id": "cv_abc123",
"hash": "e3b0c44298fc1c149afbf4c8996fb924...",
"txid": "a1b2c3d4e5f6...",
"confirmations": 3,
"block_height": 939157
}
Possible status values:
| Status | Description |
|---|---|
queued | Hash received, waiting to be included in a transaction |
broadcasted | Transaction broadcast to Bitcoin network, awaiting confirmation |
confirmed | Transaction 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.
Request Body
{
"action": "l402_timestamp_update",
"timestamp_id": "cv_abc123",
"webhook_url": "https://example.com/hook",
"notification_email": "alerts@example.com"
}
| Field | Type | Required | Description |
|---|---|---|---|
action | string | Yes | Must be "l402_timestamp_update" |
timestamp_id | string | Yes | Timestamp ID (e.g. cv_abc123def456) |
webhook_url | string | No* | HTTPS URL for status notifications. Set to null or empty to remove. |
notification_email | string | No* | Email for confirmations. Set to null or empty to remove. |
*At least one of webhook_url or notification_email must be provided.
{
"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.
{
"event": "status_change",
"timestamp_id": "cv_abc123",
"hash": "e3b0c44298fc1c149afbf4c8996fb924...",
"status": "confirmed",
"txid": "a1b2c3d4e5f6...",
"block_height": 939157
}
Webhooks fire on these transitions:
queued→broadcasted— transaction sent to Bitcoin networkbroadcasted→confirmed— transaction confirmed in a block
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
| Endpoint | Limit | Note |
|---|---|---|
| Timestamp (L402 challenge) | 5/minute per IP | L402 payment naturally limits abuse |
| File Upload (L402 challenge) | 5/minute per IP | Same as hash timestamp |
| Verify | 10/minute per IP | |
| Status | 10/minute per IP | |
| Update Notifications | 10/minute per IP |
Error Handling
All errors return JSON with success: false and an error code.
| HTTP Code | Error Code | Description |
|---|---|---|
| 400 | invalid_hash | Hash is not a valid 64-character hex string |
| 400 | missing_hash | No hash provided in request body |
| 400 | invalid_action | Unknown action specified |
| 402 | payment_required | L402 payment needed (includes invoice) |
| 429 | rate_limited | Too many requests, try again later |
| 502 | backend_unavailable | Backend service temporarily unavailable, retry |
| 500 | server_error | Internal error, try again |