Skip to main content
Beta — This feature is currently in beta. API may be subject to changes.
This document introduces the working principles, configuration methods, and best practices for ChainStream Webhooks, helping you implement real-time on-chain event delivery.
Webhook functionality is available to all users.

How It Works

Data Flow

Core Features

FeatureDescription
Real-time DeliveryMillisecond-level delivery after event trigger
Reliable DeliveryAuto-retry on failure
Signature VerificationHMAC signature anti-forgery
Filter RulesSupport event type filtering

Supported Event Types

Webhook currently supports the following event types (channels):
ChannelDescriptionTypical Use
sol.token.createdSolana new token creationNew token discovery, early opportunities
sol.token.migratedSolana token graduation/migrationTrack tokens graduating from Pump.fun and similar platforms
More event types are in development. Stay tuned!

Create Webhook Endpoint

API Endpoint

POST /v1/webhook/endpoint
Content-Type: application/json
Authorization: Bearer YOUR_ACCESS_TOKEN

Request Parameters

ParameterTypeRequiredDescription
urlstringYesWebhook callback URL (must be HTTPS)
channelsarrayYesList of event types to subscribe to
descriptionstringNoEndpoint description
disabledbooleanNoWhether disabled, default false
filterTypesarrayNoFilter types
metadataobjectNoCustom metadata
rateLimitintegerNoRate limit

Request Example

{
  "url": "https://your-server.com/webhook",
  "channels": ["sol.token.created", "sol.token.migrated"],
  "description": "Monitor new tokens and graduated tokens"
}

Response Example

{
  "id": "ep_abc123",
  "url": "https://your-server.com/webhook",
  "channels": ["sol.token.created", "sol.token.migrated"],
  "description": "Monitor new tokens and graduated tokens",
  "disabled": false
}

Webhook Notification Format

Webhook notification data structure is consistent with WebSocket push.

New Token Created (sol.token.created)

{
  "channel": "sol.token.created",
  "timestamp": 1706947200000,
  "data": {
    "a": "6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN",
    "n": "Example Token",
    "s": "EXT",
    "dec": 9,
    "cts": 1706947200000,
    "lf": {
      "pa": "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P",
      "pf": "pump_fun",
      "pn": "Pump.fun"
    }
  }
}
Field Descriptions:
FieldDescription
aToken address
nToken name
sToken symbol
decDecimals
ctsCreated timestamp (milliseconds)
lf.paLaunch platform program address
lf.pfProtocol family
lf.pnProtocol name

Token Graduated (sol.token.migrated)

{
  "channel": "sol.token.migrated",
  "timestamp": 1706947200000,
  "data": {
    "a": "6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN",
    "n": "Example Token",
    "s": "EXT",
    "cts": 1706947200000,
    "lf": {
      "pa": "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P",
      "pf": "pump_fun",
      "pn": "Pump.fun"
    },
    "mt": {
      "pa": "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8",
      "pf": "raydium",
      "pn": "Raydium"
    }
  }
}
Additional Fields:
FieldDescription
mt.paMigration target platform program address
mt.pfMigration target protocol family
mt.pnMigration target protocol name

Webhook URL Requirements

RequirementDescription
✅ HTTPSMust use HTTPS protocol
✅ Publicly AccessibleURL must be accessible from public internet
✅ 2xx ResponseMust return 2xx status code for success
✅ Response TimeShould respond within 5 seconds
✅ Idempotent HandlingMust handle duplicate requests

Security Verification

Get Webhook Secret

After creating an endpoint, get the secret via this API:
GET /v1/webhook/endpoint/{id}/secret
Response:
{
  "secret": "whsec_abcdXXX"
}

Signature Verification

Each Webhook request includes signature headers for verifying request origin:
X-Webhook-Signature: <signature>
X-Webhook-Timestamp: <timestamp>

Verification Flow

Code Examples

const crypto = require('crypto');

function verifyWebhook(req, secret) {
  const signature = req.headers['x-webhook-signature'];
  const timestamp = req.headers['x-webhook-timestamp'];
  const body = JSON.stringify(req.body);
  
  // Check timestamp (5-minute window)
  const now = Date.now();
  if (Math.abs(now - parseInt(timestamp)) > 300000) {
    return false;
  }
  
  // Calculate signature
  const message = `${timestamp}.${body}`;
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(message)
    .digest('hex');
  
  // Safe comparison
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

Manage Webhook Endpoints

List Endpoints

GET /v1/webhook/endpoint
Query Parameters:
ParameterTypeDescription
limitintegerItems per page (1-100, default 100)
iteratorstringPagination iterator
orderstringSort order (ascending/descending)

Get Endpoint Details

GET /v1/webhook/endpoint/{id}

Update Endpoint

PATCH /v1/webhook/endpoint
{
  "endpointId": "ep_abc123",
  "channels": ["sol.token.created"],
  "description": "Monitor new tokens only"
}

Delete Endpoint

DELETE /v1/webhook/endpoint/{id}

Rotate Secret

POST /v1/webhook/endpoint/{id}/secret/rotate

Best Practices

✅ Fast Response

# Recommended: Respond first, process later
@app.route('/webhook', methods=['POST'])
def webhook():
    # Verify signature
    if not verify_webhook(request, SECRET):
        return "Invalid signature", 401
    
    # Put in queue for async processing
    queue.put(request.json)
    
    # Return 200 immediately
    return "OK", 200

✅ Idempotency Handling

Each event contains a unique identifier. Record processed events on your server:
# Use Redis to record processed events
def process_webhook(event):
    event_id = f"{event['channel']}:{event['data']['a']}:{event['timestamp']}"
    
    # Check if already processed
    if redis.exists(f"processed:{event_id}"):
        return {"status": "already_processed"}
    
    # Process event
    handle_event(event)
    
    # Mark as processed (TTL 24 hours)
    redis.setex(f"processed:{event_id}", 86400, "1")
    
    return {"status": "ok"}

✅ Security

Always Verify Signature

Verify signature for every request

Use HTTPS

Ensure transport security

Rotate Secret Regularly

Recommended every 90 days

Protect Sensitive Data

Don’t log sensitive data

✅ Reliability

Implement Idempotency

Handle duplicate requests

Message Queue Buffer

Use queues for async processing

Reasonable Timeout

Avoid long blocking

Comprehensive Logging

Log key information for troubleshooting

FAQ

Troubleshooting steps:
  1. Confirm URL is accessible — Test if URL is reachable from public internet
  2. Check HTTPS — Must use a valid SSL certificate
  3. Check endpoint status — Confirm disabled is not true
  4. Check channels — Confirm subscribed to correct event types
This may be caused by retry mechanism. Implement idempotency handling:
  1. Use unique event identifier (channel + token address + timestamp)
  2. Check if already processed when receiving requests
  3. Use cache with TTL (like Redis) for storage
  1. Use ngrok to expose local service
  2. Create Webhook endpoint pointing to ngrok URL
  3. Wait for real events to trigger, or use test environment
  4. Check local service logs

API Endpoint Summary

FunctionEndpoint
List EndpointsGET /v1/webhook/endpoint
Create EndpointPOST /v1/webhook/endpoint
Update EndpointPATCH /v1/webhook/endpoint
Get Endpoint DetailsGET /v1/webhook/endpoint/{id}
Delete EndpointDELETE /v1/webhook/endpoint/{id}
Get SecretGET /v1/webhook/endpoint/{id}/secret
Rotate SecretPOST /v1/webhook/endpoint/{id}/secret/rotate