Skip to content

Instantly share code, notes, and snippets.

@Divide-By-0
Created February 6, 2026 03:21
Show Gist options
  • Select an option

  • Save Divide-By-0/00c4204d0af63aa215bb4ea0f5e94bd9 to your computer and use it in GitHub Desktop.

Select an option

Save Divide-By-0/00c4204d0af63aa215bb4ea0f5e94bd9 to your computer and use it in GitHub Desktop.

ZKP2P Pay Agent Skill - Complete Documentation

Version: 1.0.0 Last Updated: February 4, 2025 PR: https://github.com/zkp2p/pay/pull/46


Table of Contents

  1. Overview
  2. Skill Frontmatter
  3. Quick Start
  4. How It Works
  5. API Reference
  6. Code Examples
  7. Best Practices
  8. Troubleshooting

Overview

This Claude skill enables AI agents to accept fiat payments (Venmo, Zelle, Cash App, PayPal, Wise, Revolut) from humans and receive USDC on Base chain.

Key Features:

  • Programmatic merchant registration (no browser required)
  • Exact-fiat checkout sessions for predictable pricing
  • Payment status polling or webhook notifications
  • Zero-knowledge proof verification (handled by the platform)

Use Cases:

  • Pay-per-task AI services
  • Subscription billing
  • Tips and donations
  • Any agent that needs to charge users

Skill Frontmatter

---
name: zkp2p-pay-agent
description: Accept fiat payments from humans via ZKP2P Pay. Use when an agent needs to charge users money, create payment links, or accept Venmo/Zelle/CashApp payments in exchange for services.
allowed-tools: Bash(curl *), Read, Write
---

Quick Start

Step 1: Register as a Merchant (One-time)

No authentication required. Creates a new merchant and returns an API key.

curl -X POST https://api.pay.zkp2p.xyz/api/merchants \
  -H "Content-Type: application/json" \
  -d '{"name": "My AI Agent Service"}'

Response:

{
  "success": true,
  "responseObject": {
    "merchant": {
      "id": "clxxxxxxxxxxxxxxxxxx",
      "name": "My AI Agent Service",
      "createdAt": "2025-02-04T10:00:00.000Z"
    },
    "apiKey": "a]1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4"
  }
}

IMPORTANT: Store the apiKey securely - it is only returned once and cannot be retrieved later.

Step 2: Create a Payment Session

curl -X POST https://api.pay.zkp2p.xyz/api/checkout/sessions \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_API_KEY" \
  -d '{
    "merchantId": "YOUR_MERCHANT_ID",
    "checkoutMode": "exact-fiat",
    "fiatAmount": "10.00",
    "fiatCurrency": "USD",
    "destinationChainId": 8453,
    "destinationToken": "USDC",
    "recipientAddress": "0xYourWalletAddress",
    "metadata": {"orderId": "task-123", "description": "AI assistance"}
  }'

Required Fields:

Field Value Description
merchantId Your merchant ID From step 1
checkoutMode "exact-fiat" Fixed fiat amount
fiatAmount "10.00" Amount in fiat
fiatCurrency "USD" Currency code
destinationChainId 8453 Base chain
destinationToken "USDC" Must be string "USDC", NOT contract address
recipientAddress "0x..." Your EVM wallet to receive USDC

Response:

{
  "success": true,
  "responseObject": {
    "session": {
      "id": "clxxxxxxxxxxxxxxxxxx",
      "checkoutUrl": "https://pay.zkp2p.xyz/checkout/clxxxxxxxxxxxxxxxxxx",
      "status": "pending",
      "fiatAmount": "10.00",
      "fiatCurrency": "USD",
      "expiresAt": "2025-02-05T10:00:00.000Z"
    }
  }
}

Step 3: Send Checkout URL to Human

Provide the checkoutUrl to the user:

Please complete your $10.00 payment here: https://pay.zkp2p.xyz/checkout/clxxxxxxxxxxxxxxxxxx

The user will:

  1. Open the URL in their browser
  2. Select a payment platform (Venmo, Zelle, etc.)
  3. Send the fiat payment
  4. Generate a ZK proof via the PeerAuth browser extension
  5. Submit the proof

Step 4: Poll for Payment Status

curl https://api.pay.zkp2p.xyz/api/checkout/sessions/SESSION_ID

Response (pending):

{
  "success": true,
  "responseObject": {
    "session": {
      "id": "clxxxxxxxxxxxxxxxxxx",
      "status": "pending",
      "fiatAmount": "10.00"
    }
  }
}

Response (completed):

{
  "success": true,
  "responseObject": {
    "session": {
      "id": "clxxxxxxxxxxxxxxxxxx",
      "status": "completed",
      "fiatAmount": "10.00",
      "cryptoAmount": "9.95",
      "paymentPlatform": "venmo",
      "transactionHash": "0xabc123...",
      "completedAt": "2025-02-04T10:30:00.000Z"
    }
  }
}

How It Works

┌─────────────────────────────────────────────────────────────────────┐
│                        PAYMENT FLOW                                  │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  1. Agent creates checkout session                                   │
│     └─> API returns checkoutUrl                                      │
│                                                                      │
│  2. Agent sends checkoutUrl to human                                 │
│     └─> Human opens in browser                                       │
│                                                                      │
│  3. Human selects payment platform                                   │
│     └─> Venmo, Zelle, Cash App, PayPal, Wise, Revolut               │
│                                                                      │
│  4. Human sends fiat payment                                         │
│     └─> To a verified ZKP2P recipient                               │
│                                                                      │
│  5. Human generates ZK proof                                         │
│     └─> PeerAuth browser extension proves payment                   │
│     └─> Without revealing sensitive transaction data                │
│                                                                      │
│  6. Platform verifies proof & fulfills on-chain                      │
│     └─> Agent receives confirmation (poll or webhook)               │
│                                                                      │
│  7. Merchant receives USDC on Base chain                             │
│     └─> At the specified recipientAddress                           │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘

Session Statuses

Status Meaning Action
pending Awaiting payment Continue polling
awaiting_proof Fiat sent, generating ZK proof Continue polling
processing Proof verified, processing on-chain Continue polling
completed Payment successful, USDC received Deliver service
expired Session timed out (24h default) Create new session
failed Payment or proof failed Handle error

Checkout Modes

Mode Use Case Required Fields
exact-fiat Fixed USD price fiatAmount, fiatCurrency
exact-crypto Fixed USDC amount cryptoAmount
open User chooses amount (tips) None

API Reference

Base URL: https://api.pay.zkp2p.xyz

Authentication

Most endpoints require the X-API-Key header:

curl -H "X-API-Key: sk_live_xxxxx" https://api.pay.zkp2p.xyz/api/...

Response Format

All responses follow this structure:

{
  "success": true,
  "message": "Optional message",
  "responseObject": { ... },
  "statusCode": 200
}

Error responses:

{
  "success": false,
  "message": "Error description",
  "statusCode": 400
}

POST /api/merchants

Create a new merchant account.

Authentication: None required

Request:

curl -X POST https://api.pay.zkp2p.xyz/api/merchants \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My AI Agent",
    "logoUrl": "https://example.com/logo.png"
  }'
Field Type Required Description
name string Yes Merchant display name
logoUrl string No URL to logo image

Response:

{
  "success": true,
  "responseObject": {
    "merchant": {
      "id": "clxxxxxxxxxxxxxxxxxx",
      "name": "My AI Agent",
      "logoUrl": "https://example.com/logo.png",
      "createdAt": "2025-02-04T10:00:00.000Z"
    },
    "apiKey": "a1b2c3d4e5f6..."
  }
}

POST /api/checkout/sessions

Create a payment session.

Authentication: Required (X-API-Key)

Request:

curl -X POST https://api.pay.zkp2p.xyz/api/checkout/sessions \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_API_KEY" \
  -d '{
    "merchantId": "clxxxxxxxxxxxxxxxxxx",
    "checkoutMode": "exact-fiat",
    "fiatAmount": "25.00",
    "fiatCurrency": "USD",
    "destinationChainId": 8453,
    "destinationToken": "USDC",
    "recipientAddress": "0xYourWalletAddress",
    "metadata": {"orderId": "order_123"},
    "expiresIn": 3600
  }'
Field Type Required Description
merchantId string Yes Your merchant ID
checkoutMode string Yes exact-fiat, exact-crypto, or open
fiatAmount string For exact-fiat Amount (e.g., "25.00")
fiatCurrency string For exact-fiat Currency code (e.g., "USD")
cryptoAmount string For exact-crypto USDC amount
destinationChainId number Yes 8453 for Base
destinationToken string Yes "USDC" (string, NOT address)
recipientAddress string Yes* EVM wallet for USDC (*required if no default)
metadata object No Custom data (returned in webhooks)
expiresIn number No Seconds until expiration (default: 86400)
successUrl string No Redirect after success
cancelUrl string No Redirect if cancelled

Response:

{
  "success": true,
  "responseObject": {
    "session": {
      "id": "clxxxxxxxxxxxxxxxxxx",
      "merchantId": "clxxxxxxxxxxxxxxxxxx",
      "checkoutUrl": "https://pay.zkp2p.xyz/checkout/clxxxxxxxxxxxxxxxxxx",
      "status": "pending",
      "checkoutMode": "exact-fiat",
      "fiatAmount": "25.00",
      "fiatCurrency": "USD",
      "expiresAt": "2025-02-04T11:00:00.000Z",
      "createdAt": "2025-02-04T10:00:00.000Z"
    }
  }
}

GET /api/checkout/sessions/:sessionId

Get session status.

Authentication: None required (session IDs are unguessable)

Request:

curl https://api.pay.zkp2p.xyz/api/checkout/sessions/SESSION_ID

Response (completed):

{
  "success": true,
  "responseObject": {
    "session": {
      "id": "clxxxxxxxxxxxxxxxxxx",
      "status": "completed",
      "fiatAmount": "25.00",
      "fiatCurrency": "USD",
      "cryptoAmount": "24.87",
      "paymentPlatform": "venmo",
      "transactionHash": "0xabc123...",
      "completedAt": "2025-02-04T10:30:00.000Z"
    }
  }
}

POST /api/webhooks

Register a webhook URL.

Authentication: Required (X-API-Key)

Request:

curl -X POST https://api.pay.zkp2p.xyz/api/webhooks \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_API_KEY" \
  -d '{
    "merchantId": "clxxxxxxxxxxxxxxxxxx",
    "url": "https://your-agent.example.com/webhooks/payment",
    "events": ["session.completed", "session.failed"]
  }'

Event Types:

  • session.completed - Payment successful
  • session.failed - Payment failed
  • session.expired - Session timed out

Webhook Payload:

{
  "id": "evt_123456",
  "event": "session.completed",
  "createdAt": "2025-02-04T10:30:00.000Z",
  "data": {
    "sessionId": "clxxxxxxxxxxxxxxxxxx",
    "merchantId": "clxxxxxxxxxxxxxxxxxx",
    "status": "completed",
    "fiatAmount": "25.00",
    "cryptoAmount": "24.87",
    "paymentPlatform": "venmo",
    "transactionHash": "0xabc123...",
    "metadata": {"orderId": "order_123"}
  }
}

Rate Limits

Endpoint Limit
POST /api/merchants 10/hour per IP
POST /api/checkout/sessions 100/minute per API key
GET /api/checkout/sessions/:id 300/minute per IP

Error Codes

Status Code Description
400 invalid_request Invalid parameters
401 unauthorized Invalid/missing API key
404 not_found Resource doesn't exist
429 rate_limited Too many requests
500 server_error Internal error

Code Examples

Python: Complete Payment Client

import requests
import time
import os

class ZKP2PPayClient:
    """Client for ZKP2P Pay API."""

    def __init__(self, merchant_id: str, api_key: str, recipient_address: str):
        self.merchant_id = merchant_id
        self.api_key = api_key
        self.recipient_address = recipient_address
        self.base_url = "https://api.pay.zkp2p.xyz"

    def create_payment(
        self,
        amount: str,
        description: str = None,
        metadata: dict = None
    ) -> dict:
        """Create a payment session."""
        response = requests.post(
            f"{self.base_url}/api/checkout/sessions",
            headers={
                "Content-Type": "application/json",
                "X-API-Key": self.api_key
            },
            json={
                "merchantId": self.merchant_id,
                "checkoutMode": "exact-fiat",
                "fiatAmount": amount,
                "fiatCurrency": "USD",
                "destinationChainId": 8453,
                "destinationToken": "USDC",
                "recipientAddress": self.recipient_address,
                "metadata": metadata or {"description": description}
            }
        )

        data = response.json()
        if not data["success"]:
            raise Exception(f"Failed to create session: {data.get('message')}")

        session = data["responseObject"]["session"]
        return {
            "session_id": session["id"],
            "checkout_url": session["checkoutUrl"],
            "expires_at": session["expiresAt"]
        }

    def get_status(self, session_id: str) -> dict:
        """Get session status."""
        response = requests.get(
            f"{self.base_url}/api/checkout/sessions/{session_id}"
        )

        data = response.json()
        if not data["success"]:
            raise Exception(f"Failed to get session: {data.get('message')}")

        return data["responseObject"]["session"]

    def wait_for_payment(
        self,
        session_id: str,
        timeout_minutes: int = 30,
        poll_interval: int = 30
    ) -> dict:
        """Poll until payment completes or times out."""
        end_time = time.time() + (timeout_minutes * 60)

        while time.time() < end_time:
            session = self.get_status(session_id)
            status = session["status"]

            if status == "completed":
                return {
                    "success": True,
                    "crypto_amount": session.get("cryptoAmount"),
                    "platform": session.get("paymentPlatform"),
                    "tx_hash": session.get("transactionHash")
                }
            elif status in ["failed", "expired"]:
                return {"success": False, "status": status}

            print(f"Status: {status}, waiting...")
            time.sleep(poll_interval)

        return {"success": False, "status": "timeout"}


# --- Setup (one-time) ---
def setup_merchant(name: str) -> tuple[str, str]:
    """Register as a merchant. Returns (merchant_id, api_key)."""
    response = requests.post(
        "https://api.pay.zkp2p.xyz/api/merchants",
        json={"name": name}
    )
    data = response.json()

    if not data["success"]:
        raise Exception(f"Failed to create merchant: {data.get('message')}")

    merchant = data["responseObject"]["merchant"]
    api_key = data["responseObject"]["apiKey"]

    print(f"Merchant ID: {merchant['id']}")
    print(f"API Key: {api_key}")
    print("SAVE THESE - API key is only shown once!")

    return merchant["id"], api_key


# --- Usage Example ---
if __name__ == "__main__":
    # Use environment variables or setup first
    MERCHANT_ID = os.environ.get("ZKP2P_MERCHANT_ID")
    API_KEY = os.environ.get("ZKP2P_API_KEY")
    RECIPIENT = os.environ.get("ZKP2P_RECIPIENT_ADDRESS")

    if not all([MERCHANT_ID, API_KEY, RECIPIENT]):
        print("First time? Run setup:")
        merchant_id, api_key = setup_merchant("My AI Agent")
        print("\nSet environment variables:")
        print(f"export ZKP2P_MERCHANT_ID={merchant_id}")
        print(f"export ZKP2P_API_KEY={api_key}")
        print("export ZKP2P_RECIPIENT_ADDRESS=0xYourWallet")
        exit(0)

    # Create client
    client = ZKP2PPayClient(MERCHANT_ID, API_KEY, RECIPIENT)

    # Create payment
    payment = client.create_payment(
        amount="5.00",
        description="Code review service"
    )
    print(f"Pay here: {payment['checkout_url']}")

    # Wait for payment
    result = client.wait_for_payment(payment["session_id"], timeout_minutes=10)

    if result["success"]:
        print(f"Payment received! {result['crypto_amount']} USDC")
        print(f"Transaction: {result['tx_hash']}")
    else:
        print(f"Payment {result['status']}")

Bash: Polling Script

#!/bin/bash
# poll-payment.sh - Wait for payment completion

set -e

SESSION_ID="${1:?Usage: $0 <session_id>}"
BASE_URL="${ZKP2P_API_URL:-https://api.pay.zkp2p.xyz}"
MAX_ATTEMPTS=60  # 30 minutes at 30-second intervals
ATTEMPT=0

echo "Waiting for payment on session: $SESSION_ID"
echo ""

while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
    RESPONSE=$(curl -s "$BASE_URL/api/checkout/sessions/$SESSION_ID")
    STATUS=$(echo "$RESPONSE" | jq -r '.responseObject.session.status')

    case $STATUS in
        "completed")
            CRYPTO=$(echo "$RESPONSE" | jq -r '.responseObject.session.cryptoAmount')
            PLATFORM=$(echo "$RESPONSE" | jq -r '.responseObject.session.paymentPlatform')
            TX=$(echo "$RESPONSE" | jq -r '.responseObject.session.transactionHash')
            echo "✓ Payment received!"
            echo "  Amount: $CRYPTO USDC"
            echo "  Platform: $PLATFORM"
            echo "  Transaction: $TX"
            exit 0
            ;;
        "failed")
            echo "✗ Payment failed"
            exit 1
            ;;
        "expired")
            echo "✗ Session expired"
            exit 1
            ;;
        *)
            echo "Status: $STATUS - waiting... (attempt $((ATTEMPT+1))/$MAX_ATTEMPTS)"
            ;;
    esac

    sleep 30
    ((ATTEMPT++))
done

echo "✗ Timeout waiting for payment"
exit 1

Python: Pay-Per-Task Agent

class PayPerTaskAgent:
    """Agent that charges per task."""

    PRICING = {
        "code_review": "5.00",
        "bug_fix": "15.00",
        "feature": "50.00",
        "consultation": "25.00"
    }

    def __init__(self, payment_client: ZKP2PPayClient):
        self.client = payment_client
        self.pending_tasks = {}

    def request_task(self, user_id: str, task_type: str, description: str) -> dict:
        """Create a task and request payment."""
        price = self.PRICING.get(task_type, "10.00")
        task_id = f"task_{user_id}_{int(time.time())}"

        payment = self.client.create_payment(
            amount=price,
            metadata={
                "task_id": task_id,
                "task_type": task_type,
                "description": description,
                "user_id": user_id
            }
        )

        self.pending_tasks[task_id] = {
            "session_id": payment["session_id"],
            "task_type": task_type,
            "description": description
        }

        return {
            "task_id": task_id,
            "price": price,
            "payment_url": payment["checkout_url"],
            "message": f"Please pay ${price} for {task_type}: {payment['checkout_url']}"
        }

    def check_and_execute(self, task_id: str) -> dict:
        """Check if paid, then execute task."""
        task = self.pending_tasks.get(task_id)
        if not task:
            return {"error": "Task not found"}

        result = self.client.wait_for_payment(
            task["session_id"],
            timeout_minutes=5
        )

        if result["success"]:
            # Execute the task here
            output = self._execute_task(task)
            del self.pending_tasks[task_id]
            return {
                "status": "completed",
                "output": output,
                "payment": result
            }
        else:
            return {
                "status": "unpaid",
                "message": f"Payment {result['status']}. Please try again."
            }

    def _execute_task(self, task: dict) -> str:
        """Execute the actual task (implement your logic here)."""
        return f"Completed {task['task_type']}: {task['description']}"


# Usage
agent = PayPerTaskAgent(client)

# User requests a task
request = agent.request_task(
    user_id="user_123",
    task_type="code_review",
    description="Review my authentication module"
)
print(request["message"])

# After user pays...
result = agent.check_and_execute(request["task_id"])
if result["status"] == "completed":
    print(result["output"])

Best Practices

Security

  1. Store API keys securely - Use environment variables or secrets manager
  2. Never log API keys - Mask in logs if needed
  3. Validate webhook signatures - Verify X-Webhook-Signature header
  4. Use HTTPS only - All API calls should be over TLS

Reliability

  1. Handle all statuses - Don't just check for "completed"
  2. Implement retries - Use exponential backoff for network errors
  3. Set appropriate timeouts - Don't wait forever for payments
  4. Use metadata - Include task/order IDs for reconciliation

User Experience

  1. Confirm before charging - Always show amount before creating session
  2. Provide clear instructions - Tell users what to expect
  3. Handle failures gracefully - Offer retry or alternative
  4. Send reminders - Sessions expire after 24 hours

Troubleshooting

Common Errors

Error Cause Solution
401 Unauthorized Invalid/missing API key Check X-API-Key header
Only USDC is supported Wrong destinationToken Use "USDC" (string), not contract address
No default EVM wallet Missing recipientAddress Add recipientAddress to request
Invalid fiatAmount Wrong format Use string like "10.00", not number
Session expired Took too long Create a new session

Debugging Tips

  1. Check response body - Error details are in message field
  2. Verify merchant exists - GET /api/merchants/:id
  3. Check session status - GET /api/checkout/sessions/:id
  4. Review API logs - Check your agent's logs for full response

Test Script

#!/bin/bash
# test-integration.sh

BASE_URL="${ZKP2P_API_URL:-https://api.pay.zkp2p.xyz}"

echo "Testing ZKP2P Pay API..."

# Test 1: Create merchant
echo "1. Creating test merchant..."
RESPONSE=$(curl -s -X POST "$BASE_URL/api/merchants" \
  -H "Content-Type: application/json" \
  -d '{"name": "Test Agent"}')

SUCCESS=$(echo "$RESPONSE" | jq -r '.success')
if [ "$SUCCESS" = "true" ]; then
    MERCHANT_ID=$(echo "$RESPONSE" | jq -r '.responseObject.merchant.id')
    API_KEY=$(echo "$RESPONSE" | jq -r '.responseObject.apiKey')
    echo "   ✓ Merchant: $MERCHANT_ID"
    echo "   ✓ API Key: ${API_KEY:0:20}..."
else
    echo "   ✗ Failed: $(echo "$RESPONSE" | jq -r '.message')"
    exit 1
fi

# Test 2: Create session
echo "2. Creating checkout session..."
SESSION=$(curl -s -X POST "$BASE_URL/api/checkout/sessions" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $API_KEY" \
  -d "{
    \"merchantId\": \"$MERCHANT_ID\",
    \"checkoutMode\": \"exact-fiat\",
    \"fiatAmount\": \"1.00\",
    \"fiatCurrency\": \"USD\",
    \"destinationChainId\": 8453,
    \"destinationToken\": \"USDC\",
    \"recipientAddress\": \"0x0000000000000000000000000000000000000001\"
  }")

SESSION_SUCCESS=$(echo "$SESSION" | jq -r '.success')
if [ "$SESSION_SUCCESS" = "true" ]; then
    SESSION_ID=$(echo "$SESSION" | jq -r '.responseObject.session.id')
    CHECKOUT_URL=$(echo "$SESSION" | jq -r '.responseObject.session.checkoutUrl')
    echo "   ✓ Session: $SESSION_ID"
    echo "   ✓ URL: $CHECKOUT_URL"
else
    echo "   ✗ Failed: $(echo "$SESSION" | jq -r '.message')"
    exit 1
fi

# Test 3: Get status
echo "3. Checking session status..."
STATUS=$(curl -s "$BASE_URL/api/checkout/sessions/$SESSION_ID" \
  | jq -r '.responseObject.session.status')
echo "   ✓ Status: $STATUS"

echo ""
echo "========================================="
echo "All tests passed!"
echo "========================================="

Support


Generated for ClawdHub marketplace. Last updated: February 4, 2025.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment