Skip to content

ivaavimusic/Singularity-SDK

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Singularity SDK

Server-side receipt and webhook verification helpers for the x402 payment protocol — Node.js & Python

Documentation  ·  JWKS Endpoint  ·  x402 Studio


Overview

When a buyer completes an x402 payment, the worker issues a signed receipt JWT containing the transaction details. When a seller webhook fires, the worker also signs the raw webhook body with an HMAC secret. This SDK helps you verify both layers safely.

Both the Node and Python implementations handle:

  • receipt JWT verification via RS256 + JWKS
  • webhook HMAC verification
  • optional webhook-to-receipt cross-checking
  • optional source-slug binding

Installation

Node.js

npm install x402sgl
const {
  verifyX402ReceiptToken,
  createX402ReceiptMiddleware,
  verifyX402WebhookSignature,
  verifyX402WebhookEvent,
} = require('x402sgl');

Python

pip install git+https://github.com/ivaavimusic/Singularity-SDK.git#subdirectory=python
from x402layer_middleware import X402ReceiptVerifier, require_x402_receipt

Python dependencies (installed automatically): PyJWT, cryptography. FastAPI is optional — install with pip install x402layer-sdk[fastapi].


Node.js

Receipt API

const {
  verifyX402ReceiptToken,
  createX402ReceiptMiddleware,
  verifyX402WebhookSignature,
  verifyX402WebhookEvent,
} = require('x402sgl');

verifyX402ReceiptToken(token, options?)

Verifies the receipt JWT and returns the decoded claims. Throws on invalid signature, expired token, or claim mismatch.

const claims = await verifyX402ReceiptToken(token, {
  requiredSourceSlug: 'my-endpoint', // optional — prevents token replay
});

createX402ReceiptMiddleware(options?)

Express middleware that reads the token from X-X402-Receipt-Token or Authorization: Bearer, verifies it, and attaches the claims to req.x402Receipt.

app.get(
  '/v1/resource',
  createX402ReceiptMiddleware({ requiredSourceSlug: 'my-endpoint' }),
  (req, res) => {
    res.json({ data: '...', payer: req.x402Receipt.payer_wallet });
  }
);

Returns 401 if the token is missing or invalid, 403 if the source slug does not match.


Webhook API

verifyX402WebhookSignature(rawBody, signatureHeader, secret, options?)

Verifies X-X402-Signature using the shared webhook secret.

verifyX402WebhookSignature(rawBody, req.headers['x-x402-signature'], process.env.X402_WEBHOOK_SECRET)

verifyX402WebhookEvent(rawBody, signatureHeader, secret, options?)

Verifies the webhook signature, parses the JSON payload, and if data.receipt_token exists, verifies the receipt too.

const { payload, receipt } = await verifyX402WebhookEvent(
  rawBody,
  req.headers['x-x402-signature'],
  process.env.X402_WEBHOOK_SECRET,
  {
    requiredSourceSlug: 'claude',
    requireReceipt: true,
  }
)

const purchaseId = payload.data.client_reference_id

If requireReceipt: true, the helper rejects webhook payloads that do not include data.receipt_token.


Python

Receipt API

from x402layer_middleware import (
    X402ReceiptVerifier,
    require_x402_receipt,
    verify_x402_webhook_signature,
    verify_x402_webhook_event,
)

X402ReceiptVerifier

verifier = X402ReceiptVerifier(
    jwks_url="https://api.x402layer.cc/.well-known/jwks.json",  # default
    issuer="https://api.x402layer.cc",                           # default
    audience="x402layer:receipt",                                # default
)

require_x402_receipt(verifier, required_source_slug?)

FastAPI dependency that reads the token from X-X402-Receipt-Token or Authorization: Bearer, verifies it, and returns the decoded claims.

@app.get("/v1/resource")
async def resource(receipt=require_x402_receipt(verifier, required_source_slug="my-endpoint")):
    return {"payer": receipt["payer_wallet"], "amount": receipt["amount"]}

Raises HTTP 401 if the token is missing or invalid, HTTP 403 if the source slug does not match.


Webhook API

result = verify_x402_webhook_event(
    raw_body,
    signature_header,
    secret,
    verifier=X402ReceiptVerifier(),
    require_receipt=True,
)

payload = result["payload"]
receipt = result["receipt"]
purchase_id = payload["data"].get("client_reference_id")

verify_x402_webhook_signature(...) is also available if you only want HMAC verification.


Receipt Claims

Claim Type Description
event string Always "payment.succeeded"
source string "endpoint" or "product"
source_id string UUID of the paid resource
source_slug string Slug of the resource
amount string Payment amount (e.g. "1.00")
currency string Asset symbol (e.g. "USDC")
tx_hash string On-chain transaction hash
payer_wallet string Buyer wallet address
network string "base" or "solana"
client_reference_id string | null Seller-supplied correlation id, if provided
metadata object Seller-supplied correlation metadata, if provided
status string Settlement status
iat number Issued-at (Unix timestamp)
exp number Expiry (Unix timestamp)
jti string Unique receipt ID — use for idempotency

Token Contract

Property Value
Format JWT (JWS)
Algorithm RS256
JWKS URL https://api.x402layer.cc/.well-known/jwks.json
Issuer https://api.x402layer.cc
Audience x402layer:receipt

The token is delivered in the X-X402-Receipt-Token response header after a successful payment.


Webhook Payload Contract

Seller webhook payloads are signed with:

  • X-X402-Signature: t=<timestamp>,v1=<hex_hmac_sha256>

The signed message is:

  • <timestamp>.<raw_request_body>

Current payment.succeeded payloads may include:

  • data.client_reference_id
  • data.metadata
  • data.receipt_token
  • data.jwks_url

That makes deterministic purchase correlation possible for hosted checkout flows like:

/pay/request/claude?amount=1&client_reference_id=abc-123

Security

  • Always set requiredSourceSlug in production to prevent receipt replay across resources.
  • Always verify the webhook signature before parsing or trusting the payload.
  • If you rely on settlement authenticity, require and verify data.receipt_token.
  • The SDK caches JWKS keys in memory (default 5-minute TTL) to avoid excessive network calls.
  • To rotate signing keys: publish a new JWKS entry with a new kid, then update the worker private key. Tokens signed with old keys remain verifiable until they expire.

License

MIT

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages