Skip to main content

Webhooks

Webhooks deliver real-time notifications about payment events. Prism POSTs a signed JSON payload to your configured endpoint whenever a payment is completed, fails, or settles. Use webhooks to update order status, trigger fulfillment, log transactions, or sync with your backend.

Setup

1

Create an endpoint

Add an HTTP POST endpoint to your application that accepts JSON payloads.
2

Register in the Console

Go to Prism ConsoleSettings → WebhooksAdd Endpoint. Enter your URL and select the events you want to receive.
3

Copy the signing secret

The Console generates a signing secret for your endpoint. Copy it — you’ll need it to verify webhook signatures.
4

Verify signatures

Always verify the X-Prism-Signature header before processing events. See Signature Verification below.

Example Endpoint

import express from "express";
import crypto from "crypto";

const app = express();
app.use(express.raw({ type: "application/json" }));

const WEBHOOK_SECRET = process.env.PRISM_WEBHOOK_SECRET;

app.post("/webhooks/prism", (req, res) => {
  const signature = req.headers["x-prism-signature"] as string;
  const payload = req.body.toString();

  // Verify signature
  const expected = crypto
    .createHmac("sha256", WEBHOOK_SECRET)
    .update(payload)
    .digest("hex");

  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    return res.status(401).send("Invalid signature");
  }

  const event = JSON.parse(payload);

  switch (event.type) {
    case "payment.completed":
      // Payment verified and settled on-chain
      handlePaymentCompleted(event.data);
      break;
    case "payment.failed":
      // Payment verification failed
      handlePaymentFailed(event.data);
      break;
    case "settlement.completed":
      // Funds settled to merchant wallet
      handleSettlement(event.data);
      break;
  }

  // Respond 200 quickly — process asynchronously if needed
  res.status(200).send("OK");
});

Event Types

EventDescriptionTriggered When
payment.pendingPayment submitted, awaiting confirmationAfter agent submits payment on-chain
payment.completedPayment verified and settledAfter on-chain confirmation by Spectrum
payment.failedPayment verification failedInvalid transaction, wrong amount, or expired
settlement.completedFunds settled to merchant walletAfter Spectrum settlement completes

Payload Format

All webhook payloads follow the same structure:
{
  "id": "evt_abc123def456",
  "type": "payment.completed",
  "created": "2025-01-15T10:30:00Z",
  "data": {
    "payment_id": "pay_xyz789ghi012",
    "amount": "10000",
    "token": "USDC",
    "chain": "base",
    "from": "0xAgentWallet1234567890abcdef1234567890abcdef",
    "to": "0xMerchantWallet1234567890abcdef1234567890ab",
    "tx_hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
    "endpoint": "/api/premium/data",
    "status": "completed"
  }
}
FieldDescription
idUnique event identifier
typeEvent type (see table above)
createdISO 8601 timestamp
data.payment_idPrism payment/charge identifier
data.amountAmount in token base units
data.tokenToken symbol (FDUSD, USDC)
data.chainChain where settlement occurred
data.fromAgent wallet address
data.toMerchant wallet address
data.tx_hashOn-chain transaction hash
data.endpointThe merchant endpoint that triggered the charge
data.statusPayment status

Signature Verification

All webhooks include an X-Prism-Signature header containing an HMAC-SHA256 signature of the raw request body, using your webhook signing secret as the key. Always verify signatures before processing events. This prevents spoofed requests from triggering actions in your application.
import crypto from "crypto";

function verifyWebhookSignature(
  payload: string,
  signature: string,
  secret: string
): boolean {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(payload)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}
Use constant-time comparison (timingSafeEqual / hmac.compare_digest) to prevent timing attacks. Do not use === or == for signature comparison.

Retry Policy

If your endpoint returns a non-2xx response, times out, or is unreachable, Prism retries delivery with exponential backoff:
AttemptDelay After Failure
1Immediate
25 minutes
330 minutes
42 hours
524 hours
After 5 failed attempts, the event is marked as failed in the Console. You can view delivery logs and manually retry from Settings → Webhooks → Delivery Log. Best practices:
  • Respond with 200 as quickly as possible — do heavy processing asynchronously
  • Use a queue (SQS, Redis, etc.) for webhook processing in high-volume scenarios
  • Implement idempotency using the event id to handle duplicate deliveries

Testing Webhooks

  • Console test button — Send a test event from the Prism Console to verify your endpoint is reachable and responding correctly
  • Local development — Use a tunneling tool like ngrok to expose your local endpoint for testing
  • Testnet — All testnet transactions trigger real webhook deliveries, so you can test the full flow without real funds