Skip to main content
payram-webhook-integration Integrate PayRam webhook handlers for real-time payment and payout event notifications. Self-hosted, no-KYC crypto payment gateway webhooks. Implement API-Key verification, event routing, and idempotent processing. Generate handlers for Express, Next.js, FastAPI, Gin, Laravel, Spring Boot. Use when setting up payment confirmation callbacks, handling payout status updates, building event-driven payment flows, or integrating PayRam events into existing systems.
الانتقال إلى التثبيت سوق المهارات اكتشف واستكشف مهارات الذكاء الاصطناعي التي بناها المجتمع.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
نسخ Promptعرض تفاصيل Prompt يتجاوز الأمر المباشر Prompt المخصّص للمراجعة. افحص المصدر قبل تشغيله.
npx skills add https://github.com/PayRam/payram-mcp --skill payram-webhook-integrationيبقى الأمر في سطر واحد. مرّر أفقيًا لمراجعته كاملًا قبل النسخ.
تفضّل نسخة محلية؟ نزّل الملفات المتاحة حاليًا لدى SkillsMP.
تحميل Zip جاري التحميل... المزيد من هذا المستودع Set up and operate PayRam - a private, self-hosted crypto payment gateway (payment links, hosted checkout, USDC/BTC/ETH deposits, sweeps to a cold wallet you control). No signup, no KYB. Use this skill when a user wants to accept crypto payments, set up a payment gateway, create payment links, or integrate PayRam into an application.
payram-checkout-integration Integrate PayRam checkout flow into web applications. Generate payment links, embed payment pages, handle redirects, and process payment confirmations. Supports Express, Next.js, FastAPI, Laravel, Gin, Spring Boot. Use when adding crypto checkout to e-commerce, building payment forms, implementing deposit flows, or creating hosted payment pages for crypto acceptance.
The end-to-end map for an agent standing up PayRam and taking it live — install (merchant OR operator), deposit wallets, gas + sweep operations, app integration, and testing. Routes every stage to the right tool or skill and covers both install roles. Use this FIRST when asked to "set up PayRam", "get PayRam ready to take payments", "which PayRam tool/skill do I use for X", or when planning a full deployment.
المهن ذات الصلة SOC
استنادا إلى تصنيف SOC المهني
name payram-webhook-integration description Integrate PayRam webhook handlers for real-time payment and payout event notifications. Self-hosted, no-KYC crypto payment gateway webhooks. Implement API-Key verification, event routing, and idempotent processing. Generate handlers for Express, Next.js, FastAPI, Gin, Laravel, Spring Boot. Use when setting up payment confirmation callbacks, handling payout status updates, building event-driven payment flows, or integrating PayRam events into existing systems.
PayRam Webhook Setup
First time with PayRam? See payram-setup to configure your server, API keys, and wallets.
Receive real-time notifications when payments confirm, fail, or payouts complete. Webhooks eliminate polling and enable event-driven architectures.
Webhook Flow
1. Payment status changes on-chain
2. PayRam sends POST to your webhook URL
3. Your handler verifies API-Key header
4. Process event (fulfill order, update DB)
5. Return 200 OK
Configuring Webhooks in PayRam
Navigate to Settings → Webhooks in PayRam dashboard
Add your endpoint URL: https://your-app.com/api/payram/webhook
Copy the shared webhook secret
Store secret as PAYRAM_WEBHOOK_SECRET in your .env
Webhook Payload
PayRam sends webhook requests with an API-Key header for verification:
POST https://your-domain.com/api/payram/webhook
Content-Type: application/json
API-Key: your-webhook-secret
{
"reference_id": "ref_abc123",
"invoice_id": "inv_xyz456",
"customer_id": "cust_123",
"customer_email": "customer@example.com",
"status": "FILLED",
"amount": 49.99,
"filled_amount_in_usd": 49.99,
"currency": "USD"
}
Critical: Verify the API-Key header matches your stored PAYRAM_WEBHOOK_SECRET before processing.
Payment Status Events Status Meaning OPENPayment created, awaiting customer action FILLEDPayment completed successfully (exact amount paid) PARTIALLY_FILLEDPartial payment received (less than requested) OVER_FILLEDOverpayment received (more than requested) CANCELLEDPayment cancelled by customer or merchant UNDEFINEDUnknown status (future compatibility)
TypeScript Type Definitions export type PayramWebhookStatus =
| 'OPEN'
| 'CANCELLED'
| 'FILLED'
| 'PARTIALLY_FILLED'
| 'OVER_FILLED'
| 'UNDEFINED' ;
export interface PayramWebhookPayload {
reference_id : string ;
invoice_id ?: string ;
customer_id ?: string ;
customer_email ?: string ;
status : PayramWebhookStatus ;
amount ?: number ;
filled_amount_in_usd ?: number ;
currency ?: string ;
[key : string ]: unknown ;
}
Event Router export async function handlePayramEvent (payload : PayramWebhookPayload ) {
switch (payload.status ) {
case 'FILLED' :
await fulfillOrder (payload.reference_id );
break ;
case 'PARTIALLY_FILLED' :
break ;
case 'OVER_FILLED' :
break ;
case 'CANCELLED' :
break ;
case 'OPEN' :
break ;
default :
console .warn ('Unknown status:' , payload.status );
break ;
}
}
Framework Handlers
Express.js import express, { Request , Response } from 'express' ;
import crypto from 'crypto' ;
const router = express.Router ();
router.use (express.json ());
router.post ('/api/payram/webhook' , async (req : Request , res : Response ) => {
const sharedSecret = process.env .PAYRAM_WEBHOOK_SECRET ;
if (!sharedSecret) {
return res.status (500 ).json ({ error : 'webhook_not_configured' });
}
const incomingKey = req.get ('API-Key' );
if (!incomingKey) {
return res.status (401 ).json ({ error : 'invalid-webhook-key' });
}
const isValid = crypto.timingSafeEqual (Buffer .from (incomingKey), Buffer .from (sharedSecret));
if (!isValid) {
return res.status (401 ).json ({ error : 'invalid-webhook-key' });
}
const payload = req.body ;
if (!payload?.reference_id || !payload?.status ) {
return res.status (400 ).json ({ error : 'invalid-webhook-payload' });
}
try {
await handlePayramEvent (payload);
return res.json ({ message : 'Webhook received successfully' });
} catch (error) {
console .error ('Webhook handler error:' , error);
return res.status (500 ).json ({ error : 'webhook_handler_error' });
}
});
Next.js App Router import { NextRequest , NextResponse } from 'next/server' ;
import crypto from 'crypto' ;
export async function POST (request : NextRequest ) {
const sharedSecret = process.env .PAYRAM_WEBHOOK_SECRET ;
if (!sharedSecret) {
return NextResponse .json ({ error : 'webhook_not_configured' }, { status : 500 });
}
const incomingKey = request.headers .get ('API-Key' );
if (!incomingKey) {
return NextResponse .json ({ error : 'invalid-webhook-key' }, { status : 401 });
}
const isValid = crypto.timingSafeEqual (Buffer .from (incomingKey), Buffer .from (sharedSecret));
if (!isValid) {
return NextResponse .json ({ error : 'invalid-webhook-key' }, { status : 401 });
}
const payload = await request.json ();
if (!payload.reference_id || !payload.status ) {
return NextResponse .json ({ error : 'invalid-webhook-payload' }, { status : 400 });
}
try {
await handlePayramEvent (payload);
return NextResponse .json ({ message : 'Webhook received successfully' });
} catch (error) {
return NextResponse .json ({ error : 'webhook_handler_error' }, { status : 500 });
}
}
FastAPI (Python) import os
import hmac
from fastapi import FastAPI, HTTPException, Request
app = FastAPI()
@app.post('/api/payram/webhook' )
async def payram_webhook (request: Request ):
shared_secret = os.getenv('PAYRAM_WEBHOOK_SECRET' )
if not shared_secret:
raise HTTPException(status_code=500 , detail='webhook_not_configured' )
incoming_key = request.headers.get('API-Key' )
if not incoming_key:
raise HTTPException(status_code=401 , detail='invalid-webhook-key' )
if not hmac.compare_digest(incoming_key, shared_secret):
raise HTTPException(status_code=401 , detail='invalid-webhook-key' )
payload = await request.json()
if 'reference_id' not in payload or 'status' not in payload:
raise HTTPException(status_code=400 , detail='invalid-webhook-payload' )
await handle_payram_event(payload)
return {'message' : 'Webhook received successfully' }
Gin (Go) import (
"crypto/subtle"
"net/http"
"os"
"github.com/gin-gonic/gin"
)
func handlePayramWebhook (c *gin.Context) {
sharedSecret := os.Getenv("PAYRAM_WEBHOOK_SECRET" )
if sharedSecret == "" {
c.JSON(http.StatusInternalServerError, gin.H{"error" : "webhook_not_configured" })
return
}
incomingKey := c.GetHeader("API-Key" )
if incomingKey == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error" : "invalid-webhook-key" })
return
}
if subtle.ConstantTimeCompare([]byte (incomingKey), []byte (sharedSecret)) != 1 {
c.JSON(http.StatusUnauthorized, gin.H{"error" : "invalid-webhook-key" })
return
}
var payload PayramWebhookPayload
if err := c.ShouldBindJSON(&payload); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error" : "invalid-json-payload" })
return
}
if err := handlePayramEvent(payload); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error" : "webhook_handler_error" })
return
}
c.JSON(http.StatusOK, gin.H{"message" : "Webhook received successfully" })
}
Laravel (PHP) class PayramWebhookController extends Controller
{
public function handle (Request $request )
{
$sharedSecret = env ('PAYRAM_WEBHOOK_SECRET' );
if (!$sharedSecret ) {
return response ()->json (['error' => 'webhook_not_configured' ], 500 );
}
$incomingKey = $request ->header ('API-Key' );
if (!$incomingKey ) {
return response ()->json (['error' => 'invalid-webhook-key' ], 401 );
}
if (!hash_equals ($sharedSecret , $incomingKey )) {
return response ()->json (['error' => 'invalid-webhook-key' ], 401 );
}
$payload = $request ->json ()->all ();
if (empty ($payload ['reference_id' ]) || empty ($payload ['status' ])) {
return response ()->json (['error' => 'invalid-webhook-payload' ], 400 );
}
$this ->router->handle ($payload );
return response ()->json (['message' => 'Webhook received successfully' ]);
}
}
Spring Boot (Java) import java.security.MessageDigest;
import java.nio.charset.StandardCharsets;
@PostMapping("/webhook")
public ResponseEntity<?> handleWebhook(
@RequestBody Map<String, Object> payload,
@RequestHeader(value = "API-Key", required = false) String apiKey) {
String sharedSecret = System.getenv("PAYRAM_WEBHOOK_SECRET" );
if (sharedSecret == null || sharedSecret.isBlank()) {
return ResponseEntity.status(500 ).body(Map.of("error" , "webhook_not_configured" ));
}
if (apiKey == null || apiKey.isBlank()) {
return ResponseEntity.status(401 ).body(Map.of("error" , "invalid-webhook-key" ));
}
boolean isValid = MessageDigest.isEqual(
apiKey.getBytes(StandardCharsets.UTF_8),
sharedSecret.getBytes(StandardCharsets.UTF_8)
);
if (!isValid) {
return ResponseEntity.status(401 ).body(Map.of("error" , "invalid-webhook-key" ));
}
if (!payload.containsKey("reference_id" ) || !payload.containsKey("status" )) {
return ResponseEntity.status(400 ).body(Map.of("error" , "invalid-webhook-payload" ));
}
router.handle(payload);
return ResponseEntity.ok(Map.of("message" , "Webhook received successfully" ));
}
Best Practices Idempotency : Handle duplicate deliveries gracefully — check if already processed before fulfilling:
async function handleFilledPayment (payload : PayramWebhookPayload ) {
const existing = await db.payments .findUnique ({
where : { payramReferenceId : payload.reference_id },
});
if (existing && existing.status === 'completed' ) {
return ;
}
await db.payments .update ({
where : { payramReferenceId : payload.reference_id },
data : { status : 'completed' , paidAt : new Date () },
});
await fulfillOrder (payload.customer_id , payload.reference_id );
}
Quick Response : Return 200 immediately, process asynchronously. PayRam retries on timeout.
Retry Handling : If you don't return 2xx, PayRam retries on a fixed schedule — 30m, 1h, 2h, 4h, 8h, 24h, 48h (7 attempts, then marked failed; resend manually from the dashboard). Return 200 once you've durably accepted the event to stop retries.
Database Transactions : Use transactions for critical operations to ensure consistency.
Testing Webhooks
cURL Test curl -X POST http://localhost:3000/api/payram/webhook \
-H "Content-Type: application/json" \
-H "API-Key: $PAYRAM_WEBHOOK_SECRET " \
-d '{
"reference_id": "ref_test_001",
"status": "FILLED",
"customer_id": "cust_123",
"amount": 49.99,
"filled_amount_in_usd": 49.99,
"currency": "USD"
}'
MCP Server Tools Tool Purpose generate_webhook_handlerFramework-specific handler code generate_webhook_event_routerFan-out router for multiple event types generate_mock_webhook_eventTest payloads for each event type
Environment Variables PAYRAM_WEBHOOK_SECRET=your-webhook-secret-from-dashboard
All PayRam Skills Skill What it covers payram-setupServer config, API keys, wallet setup, connectivity test payram-agent-onboardingAgent onboarding — CLI-only deployment for AI agents, no web UI payram-analyticsAnalytics dashboards, reports, and payment insights via MCP tools payram-crypto-paymentsArchitecture overview, why PayRam, MCP tools payram-payment-integrationQuick-start payment integration guide payram-self-hosted-payment-gatewayDeploy and own your payment infrastructure payram-checkout-integrationCheckout flow with SDK + HTTP for 6 frameworks payram-webhook-integrationWebhook handlers for Express, Next.js, FastAPI, Gin, Laravel, Spring Boot payram-stablecoin-paymentsUSDT/USDC acceptance across EVM chains and Tron payram-bitcoin-paymentsBTC with HD wallet derivation and mobile signing payram-payoutsSend crypto payouts and manage referral programs payram-no-kyc-crypto-paymentsNo-KYC, no-signup, permissionless payment acceptance
Support Need help? Message the PayRam team on Telegram: @PayRamChat