1

Configure payment notifications

Choose how you want to be notified when payments are received.Configure a webhook to receive real-time updates on payment status. This is ideal for automated systems and immediate order fulfillment.
  1. Go to your Webhook Configuration page
  2. Add your endpoint URL
  3. Select the events you want to receive
  4. Save your configuration

Option B: Email notifications

Set up email notifications for manual order processing and simple setups.
  1. Visit the Notification Settings page
  2. Enter your email address
  3. Save your settings
2

Get your merchant wallet address

Your merchant wallet address is where all payments will be sent.
  1. Navigate to the Merchant Configuration page in your dashboard and copy your Merchant Wallet Address
Keep your wallet address secure and only share it through your payment button implementation.
3

Add the payment button to your website

Choose your preferred implementation method:
Perfect for Next.js applications.

"use client";

import { useState, useCallback } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { toast } from "sonner";
import { useAuth } from "@clerk/nextjs";
import { useWalletWithAuth } from "@/hooks/useWalletWithAuth";
import { useTransfer } from "@/hooks/useTransfer";
import { useRecordSendTransaction } from "@/hooks/useRecordSendTransaction";
import type { WalletData, Chain, ChainToken } from "@chipi-stack/types";

export function PayCryptoButton({
costumerWallet,
amountUsd,
onSuccess,
label,
}: {
costumerWallet: WalletData;
amountUsd: number;
onSuccess?: (txHash: string) => void;
label: string;
}) {
const { getToken } = useAuth();
const { wallet: senderWallet } = useWalletWithAuth();
const { transferAsync } = useTransfer();
const { recordSendTransactionAsync } = useRecordSendTransaction();

const [pin, setPin] = useState("");
const [busy, setBusy] = useState(false);

const runPayment = useCallback(
  async (pin: string) => {
    const jwtToken = await getToken();
    if (!jwtToken) {
      toast.error("No token found");
      return;
    }

    const merchant = process.env.NEXT_PUBLIC_MERCHANT_WALLET || "";
    if (!merchant) {
      toast.error("Merchant wallet is not configured");
      return;
    }

    try {
      setBusy(true);
      const transactionHash = await transferAsync({
        params: {
          amount: amountUsd.toString(),
          encryptKey: pin,
          wallet: costumerWallet,
          token: "USDC",
          recipient: merchant,
        },
        bearerToken: jwtToken,
      });

      const recordSendTransactionParams = {
        transactionHash,
        chain: "STARKNET" as Chain,
        expectedSender: senderWallet?.publicKey || "",
        expectedRecipient: merchant,
        expectedToken: "USDC" as ChainToken,
        expectedAmount: amountUsd.toString(),
      };

      const sendTx = await recordSendTransactionAsync({
        params: recordSendTransactionParams,
        bearerToken: jwtToken,
      });

      console.log("successful record send transaction:", sendTx);
      toast.success("Pago completado ✨", { position: "bottom-center" });
      onSuccess?.(transactionHash);
    } catch (err: unknown) {
      console.error("Error in pay with chipi button:", err);
      const msg =
        (typeof err === "string" && err) ||
        (err instanceof Error ? err.message : "Something went wrong. Please try again.");
      toast.error(msg, { position: "bottom-center" });
    } finally {
      setBusy(false);
    }
  },
  [
    amountUsd,
    costumerWallet,
    getToken,
    recordSendTransactionAsync,
    senderWallet?.publicKey,
    transferAsync,
    onSuccess,
  ]
);

return (
  <div className="flex flex-col gap-3 w-full max-w-sm">
    <label className="font-medium">{label}</label>
    <Input
      type="password"
      placeholder="Enter PIN"
      value={pin}
      onChange={(e) => setPin(e.target.value)}
    />
    <Button
      className="w-full"
      disabled={busy}
      onClick={() => runPayment(pin)}
    >
      {busy ? "Processing..." : "Pay Now"}
    </Button>
  </div>
);
}

The usdAmount parameter is optional. If you don’t include it, customers can choose how much they want to pay, making it perfect for donations or flexible pricing.
4

Handle webhook notifications

When a payment is successful, you’ll receive a webhook notification with the transaction details.

Webhook payload schema

event
string
required
The event type. Currently only "transaction.sent" is supported.
data
object
required
Container object for the transaction data.

Webhook signature verification

For security, all webhooks include an HMAC signature in the chipi-signature header. You should verify this signature to ensure the webhook is from ChipiPay.
  1. Get your webhook signing secret from your webhook configuration page (it looks like whsec_****)
  2. Create a webhook handler in your Next.js app:
// app/api/webhooks/route.ts
import { createHmac, timingSafeEqual } from 'crypto';
import { NextRequest, NextResponse } from 'next/server';

function verifyWebhookSignature(
  payload: string,
  receivedSignature: string,
  secretKey: string
): boolean {
  try {
    const expectedSignature = createHmac("sha256", secretKey)
      .update(payload)
      .digest("hex");

    // Use timing-safe comparison to prevent timing attacks
    return timingSafeEqual(
      Buffer.from(expectedSignature, "hex"),
      Buffer.from(receivedSignature, "hex")
    );
  } catch (error) {
    return false;
  }
}

export async function POST(request: NextRequest) {
  const payload = await request.text();
  const signature = request.headers.get('chipi-signature');
  const webhookSecret = process.env.CHIPI_WEBHOOK_SECRET!; // Your whsec_**** key

  if (!signature || !verifyWebhookSignature(payload, signature, webhookSecret)) {
    return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
  }

  const event = JSON.parse(payload);

  if (event.event === 'transaction.sent' && event.data.transaction.status === 'SUCCESS') {
    // Handle successful payment
    const transaction = event.data.transaction;
    
    // Update your database, send confirmation emails, fulfill orders, etc.
    console.log(`Payment received: ${transaction.amount} USDC from ${transaction.senderAddress}`);
  }

  return NextResponse.json({ received: true });
}
5

Celebrate

Your integration is now ready for production!