Skip to main content
Kibble monitors on-chain USDC transfers on Base using Alchemy address-activity webhooks. When you create a payment link or invoice, Kibble registers the deposit address with Alchemy automatically — you do not configure this yourself. When a transfer arrives, Kibble classifies it, updates the payment status, and — for invoices with a webhook_url — sends a signed POST request to your server.
You do not interact with Alchemy directly. Kibble manages the webhook infrastructure on your behalf. The outbound webhooks described on this page are the ones Kibble sends to you, not the other way around.

How payment detection works

When Alchemy detects a USDC transfer to a Kibble deposit address on Base, Kibble:
  1. Validates that the transferred token is USDC on Base. Transfers of any other token are silently ignored.
  2. Compares the received amount to the expected_amount for the payment link or invoice.
  3. Updates the payment status accordingly.
  4. Sends a merchant email notification confirming the payment.
  5. If the invoice has a webhook_url, fires a signed POST to that URL with the event payload.

Payment classification

Received vs. expectedResulting status
Exactly equalconfirmed (payment links) / paid (invoices)
Less than expectedpartial
More than expectedexcess
Only USDC on Base is accepted. Payments in other tokens or on other networks are silently discarded — Kibble does not return an error to the sender.

Outbound webhook payload

Kibble sends a signed POST request to your webhook_url when an invoice reaches paid, partial, or excess status. The request body is JSON.

Headers

HeaderValue
X-Kibble-Signaturesha256={hmac_hex} — HMAC-SHA256 signature of the raw request body
Content-Typeapplication/json

Payload fields

event
string
required
The event type. Currently always "invoice.paid".
invoice_id
string
required
The UUID of the invoice that was paid.
invoice_number
string
required
The human-readable invoice number (e.g. "INV-0001").
total_amount
string
required
The invoice’s total expected amount in USDC (e.g. "1500.00").
status
string
required
The resulting payment status: paid, partial, or excess.

Example payload

{
  "event": "invoice.paid",
  "invoice_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "invoice_number": "INV-0001",
  "total_amount": "1500.00",
  "status": "paid"
}

Verifying the signature

Kibble signs each webhook request with the webhook_secret returned when you created the invoice. Verify the X-Kibble-Signature header before processing any event. Use the raw request body bytes — not a parsed object — when computing the HMAC. Parsing the JSON before verification will cause signature mismatches.
import crypto from 'crypto';

function verifyWebhook(rawBody, signatureHeader, secret) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  return expected === signatureHeader;
}

// Express example
app.post('/webhooks/kibble', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-kibble-signature'];
  const isValid = verifyWebhook(req.body, signature, process.env.KIBBLE_WEBHOOK_SECRET);

  if (!isValid) {
    return res.status(401).send('Invalid signature');
  }

  const event = JSON.parse(req.body);
  // Handle event.status: 'paid' | 'partial' | 'excess'
  res.sendStatus(200);
});
Always use a constant-time comparison (such as crypto.timingSafeEqual in Node.js or hmac.compare_digest in Python) when comparing signatures. Standard equality checks are vulnerable to timing attacks.