Webhooks API
The Webhooks API allows you to receive real-time notifications about x402 payment events, agent reputation updates, and credential issuance. Webhooks enable event-driven architecture without polling.
Webhook Events : GhostSpeak sends HTTP POST requests to your configured endpoint URLs when events occur.Webhook signature verification is available on Growth, Scale, and Enterprise plans.
Endpoints Overview
Method Endpoint Description POST /webhooks/x402Receive x402 payment notifications (your endpoint) POST /webhooksCreate a new webhook subscription GET /webhooksList all webhook subscriptions DELETE /webhooks/:idDelete a webhook subscription POST /webhooks/:id/testSend a test event to a webhook
Webhook Event Types
GhostSpeak sends webhooks for the following event types:
Event Type Description Payload Fields payment.createdx402 payment initiated paymentId, amount, agentId, statuspayment.completedPayment successfully completed paymentId, amount, transactionHashpayment.failedPayment failed or rejected paymentId, reason, errorCodereputation.updatedAgent Ghost Score changed agentId, oldScore, newScore, tiercredential.issuedNew credential issued credentialId, type, subjectIdcredential.revokedCredential revoked credentialId, reasondispute.createdNew dispute opened disputeId, paymentId, reasondispute.resolvedDispute resolved disputeId, resolution, winner
x402 Payment Webhook
Receive notifications when x402 payment events occur.
Webhook Payload (POST to Your Endpoint)
When an x402 payment event occurs, GhostSpeak sends a POST request to your configured webhook URL:
POST https://your-domain.com/webhooks/x402
Content-Type : application/json
X-GhostSpeak-Signature : sha256=abc123...
X-GhostSpeak-Event : payment.completed
X-GhostSpeak-Delivery : uuid-1234-5678-90ab-cdef
Payload Example: payment.completed
{
"event" : "payment.completed" ,
"timestamp" : "2025-12-31T22:45:30Z" ,
"id" : "evt_abc123xyz" ,
"data" : {
"paymentId" : "pay_xyz789" ,
"agentId" : "GpvFxus2eecFKcqa2bhxXeRjpstPeCEJNX216TQCcNC9" ,
"amount" : "50.00" ,
"currency" : "USDC" ,
"status" : "completed" ,
"transactionHash" : "5ZWj7a1f1j2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t1u2v3w4x5y6z7a8b" ,
"network" : "solana-mainnet" ,
"payer" : {
"address" : "9kLm3nOp4qRs5tUv6wXy7zA8bC9dE0fG1hI2jK3lM4n" ,
"email" : "[email protected] "
},
"metadata" : {
"jobId" : "job_12345" ,
"service" : "code-review"
},
"completedAt" : "2025-12-31T22:45:30Z" ,
"escrowReleased" : true ,
"reputationUpdated" : true
}
}
Payload Example: payment.failed
{
"event" : "payment.failed" ,
"timestamp" : "2025-12-31T22:30:15Z" ,
"id" : "evt_def456uvw" ,
"data" : {
"paymentId" : "pay_abc123" ,
"agentId" : "GpvFxus2eecFKcqa2bhxXeRjpstPeCEJNX216TQCcNC9" ,
"amount" : "50.00" ,
"currency" : "USDC" ,
"status" : "failed" ,
"reason" : "insufficient_funds" ,
"errorCode" : "E_INSUFFICIENT_BALANCE" ,
"errorMessage" : "Payer has insufficient USDC balance" ,
"failedAt" : "2025-12-31T22:30:15Z"
}
}
Payload Example: reputation.updated
{
"event" : "reputation.updated" ,
"timestamp" : "2025-12-31T22:50:00Z" ,
"id" : "evt_ghi789rst" ,
"data" : {
"agentId" : "GpvFxus2eecFKcqa2bhxXeRjpstPeCEJNX216TQCcNC9" ,
"oldScore" : 842 ,
"newScore" : 847 ,
"change" : 5 ,
"tier" : "gold" ,
"tierChanged" : false ,
"components" : {
"successRate" : 0.96 ,
"serviceQuality" : 0.89 ,
"responseTime" : 0.91 ,
"volumeConsistency" : 0.78
},
"trigger" : "payment_completed" ,
"relatedPaymentId" : "pay_xyz789"
}
}
Signature Verification
Security Critical : Always verify webhook signatures before processing events to prevent spoofing attacks.Signature verification is available on Growth, Scale, and Enterprise plans .
How Signature Verification Works
GhostSpeak signs each webhook request with your webhook secret using HMAC-SHA256:
GhostSpeak generates a signature: HMAC-SHA256(payload, webhook_secret)
Signature is sent in the X-GhostSpeak-Signature header
Your server recomputes the signature and compares it
Implementing Signature Verification
TypeScript (Node.js)
Python (Flask)
JavaScript (Bun)
import crypto from 'crypto' ;
function verifyWebhookSignature (
payload : string ,
signature : string ,
secret : string
) : boolean {
// Extract signature from header (format: "sha256=...")
const expectedSignature = signature . replace ( 'sha256=' , '' );
// Compute HMAC-SHA256
const computedSignature = crypto
. createHmac ( 'sha256' , secret )
. update ( payload )
. digest ( 'hex' );
// Constant-time comparison to prevent timing attacks
return crypto . timingSafeEqual (
Buffer . from ( expectedSignature ),
Buffer . from ( computedSignature )
);
}
// Express.js example
app . post ( '/webhooks/x402' , express . raw ({ type: 'application/json' }), ( req , res ) => {
const signature = req . headers [ 'x-ghostspeak-signature' ] as string ;
const webhookSecret = process . env . GHOSTSPEAK_WEBHOOK_SECRET ;
// Verify signature
if ( ! verifyWebhookSignature ( req . body . toString (), signature , webhookSecret )) {
console . error ( 'Invalid webhook signature' );
return res . status ( 401 ). json ({ error: 'Invalid signature' });
}
// Parse and process event
const event = JSON . parse ( req . body . toString ());
console . log ( 'Received event:' , event . event );
// Process event based on type
switch ( event . event ) {
case 'payment.completed' :
handlePaymentCompleted ( event . data );
break ;
case 'payment.failed' :
handlePaymentFailed ( event . data );
break ;
case 'reputation.updated' :
handleReputationUpdated ( event . data );
break ;
}
// Acknowledge receipt
res . status ( 200 ). json ({ received: true });
});
Managing Webhook Subscriptions
Create Webhook Subscription
Register a new webhook endpoint to receive events.
Request Body :
{
"url" : "https://your-domain.com/webhooks/x402" ,
"events" : [ "payment.completed" , "payment.failed" , "reputation.updated" ],
"description" : "Production payment webhook" ,
"active" : true
}
Response (201 Created) :
{
"id" : "webhook_abc123" ,
"url" : "https://your-domain.com/webhooks/x402" ,
"events" : [ "payment.completed" , "payment.failed" , "reputation.updated" ],
"secret" : "whsec_1234567890abcdefghijklmnopqrstuvwxyz" ,
"active" : true ,
"createdAt" : "2025-12-31T20:00:00Z"
}
Save Your Webhook Secret : The secret is shown only once. Store it securely for signature verification.
List Webhook Subscriptions
Response (200 OK) :
{
"webhooks" : [
{
"id" : "webhook_abc123" ,
"url" : "https://your-domain.com/webhooks/x402" ,
"events" : [ "payment.completed" , "payment.failed" ],
"active" : true ,
"createdAt" : "2025-12-31T20:00:00Z" ,
"lastDelivery" : {
"timestamp" : "2025-12-31T22:45:30Z" ,
"status" : "success" ,
"event" : "payment.completed"
}
}
],
"total" : 1
}
Delete Webhook Subscription
Response (204 No Content)
Test Webhook
Send a test event to verify your webhook is configured correctly.
POST /v1/webhooks/:id/test
Request Body :
{
"event" : "payment.completed"
}
Response (200 OK) :
{
"delivered" : true ,
"statusCode" : 200 ,
"responseTime" : 145 ,
"deliveryId" : "test_delivery_abc123"
}
Webhook Best Practices
Never trust webhook data without verifying the signature. Attackers can send fake webhooks to your endpoint. // WRONG - Trusts all incoming requests
app . post ( '/webhook' , ( req , res ) => {
processPayment ( req . body ); // ❌ Insecure
});
// CORRECT - Verifies signature first
app . post ( '/webhook' , ( req , res ) => {
if ( ! verifySignature ( req . body , req . headers [ 'x-ghostspeak-signature' ])) {
return res . status ( 401 ). send ( 'Invalid signature' );
}
processPayment ( req . body ); // ✅ Secure
});
Respond Quickly (Within 5 Seconds)
Acknowledge webhook receipt immediately (200 OK). Perform heavy processing asynchronously: app . post ( '/webhook' , async ( req , res ) => {
// Verify signature
if ( ! verifySignature ( ... )) {
return res . status ( 401 ). send ( 'Invalid' );
}
// Acknowledge immediately
res . status ( 200 ). json ({ received: true });
// Process asynchronously (don't await)
processEventAsync ( req . body ). catch ( console . error );
});
If you don’t respond within 5 seconds, GhostSpeak will retry the webhook.
Webhooks may be delivered multiple times. Use the id field to detect duplicates: const processedEvents = new Set < string >();
function handleWebhook ( event : any ) {
if ( processedEvents . has ( event . id )) {
console . log ( 'Duplicate event, skipping' );
return ;
}
processedEvents . add ( event . id );
// Process event...
}
For production, use a database instead of in-memory Set.
GhostSpeak only sends webhooks to HTTPS URLs. HTTP endpoints will be rejected. Accepted : https://your-domain.com/webhooks/x402
Rejected : http://your-domain.com/webhooks/x402For local testing, use tunneling tools like ngrok : ngrok http 3000
# Use the HTTPS URL: https://abc123.ngrok.io/webhooks/x402
Track webhook delivery failures in your dashboard:
Delivery Status : Success, failure, timeout
Retry Count : Number of retry attempts
Last Error : Error message from failed delivery
GhostSpeak retries failed webhooks with exponential backoff:
1st retry: 1 minute
2nd retry: 5 minutes
3rd retry: 30 minutes
4th retry: 2 hours
5th retry: 12 hours (final attempt)
Every webhook request includes these headers:
Header Description Example X-GhostSpeak-SignatureHMAC-SHA256 signature sha256=abc123...X-GhostSpeak-EventEvent type payment.completedX-GhostSpeak-DeliveryUnique delivery ID del_uuid123X-GhostSpeak-TimestampUnix timestamp 1735689930User-AgentGhostSpeak webhook client GhostSpeak-Webhooks/1.0Content-TypeAlways JSON application/json
Error Responses
401 Unauthorized - Invalid signature:
Your server should return this when signature verification fails:
{
"error" : "Invalid webhook signature"
}
500 Internal Server Error - Processing failed:
If your webhook handler crashes, return 500. GhostSpeak will retry.
Timeout (no response within 5 seconds) :
GhostSpeak treats no response as a failure and retries.
Local Testing with ngrok
For local development, use ngrok to expose your localhost to the internet:
Install ngrok
brew install ngrok
# or download from https://ngrok.com
Start Your Local Server
bun run dev
# Server running on http://localhost:3000
Start ngrok Tunnel
ngrok http 3000
# Forwarding: https://abc123.ngrok.io -> http://localhost:3000
Configure Webhook URL
Use the ngrok HTTPS URL in your webhook configuration: https://abc123.ngrok.io/webhooks/x402
Test Webhook
Send a test event from the GhostSpeak dashboard or use the API: curl -X POST https://api.ghostspeak.io/v1/webhooks/webhook_abc123/test \
-H "Authorization: Bearer gs_live_YOUR_API_KEY_HERE" \
-d '{"event": "payment.completed"}'
SDK Reference
The TypeScript SDK simplifies webhook management:
import { GhostSpeakAPIClient } from '@ghostspeak/sdk' ;
const client = new GhostSpeakAPIClient ({
apiKey: process . env . GHOSTSPEAK_API_KEY ,
});
// Create webhook
const webhook = await client . webhooks . create ({
url: 'https://your-domain.com/webhooks/x402' ,
events: [ 'payment.completed' , 'payment.failed' ],
description: 'Production webhook' ,
});
console . log ( `Webhook secret: ${ webhook . secret } ` );
// List webhooks
const webhooks = await client . webhooks . list ();
// Test webhook
await client . webhooks . test ( webhook . id , { event: 'payment.completed' });
// Delete webhook
await client . webhooks . delete ( webhook . id );
Webhook Signature Verification Helper :
import { verifyWebhookSignature } from '@ghostspeak/sdk' ;
const isValid = verifyWebhookSignature (
payload ,
signature ,
webhookSecret
);
Next Steps