Skip to content
JuicePay

JuicePay documentation

Create account

Documentation

POST

Webhook events

Every event type with its payload shape.

Webhook endpoints receive signed event deliveries. This page is the reference for registration and every event type.

Register an endpoint#

bash
curl -s -X POST "$JUICEPAY_URL/v1/webhook_endpoints" \
  -H "Authorization: Bearer $JUICEPAY_KEY" \
  -d '{
    "url": "https://api.example.com/webhooks/juicepay",
    "enabled_events": ["payment_intent.succeeded", "payout.settled"],
    "description": "Production consumer"
  }'

The response includes the signing secret, shown only once.

json
{
  "id": "we_5mK2",
  "url": "https://api.example.com/webhooks/juicepay",
  "status": "enabled",
  "enabled_events": ["payment_intent.succeeded", "payout.settled"],
  "secret": "whsec_4mW8bZ2nHq6Yc1Pd9fKxQ7mLpVn8Rt"
}

Use "enabled_events": ["*"] to receive everything. Subscribe narrowly in production — broad subscriptions make it easy to fall behind during batch runs.

Delivery headers#

HeaderValue
JuicePay-Signaturet=<unix>,v1=<hmac-sha256>
JuicePay-EventEvent type
JuicePay-DeliveryUnique delivery ID, useful for log correlation
JuicePay-Attempt1-based attempt counter

Envelope#

json
{
  "id": "evt_2xNpRt8kL3mQ",
  "type": "payment_intent.succeeded",
  "created": "2026-08-19T09:14:02Z",
  "livemode": true,
  "data": {
    "object": {
      "id": "pi_9fK2xQ",
      "status": "succeeded",
      "amount": "4250.00",
      "asset": "USDC",
      "amount_received": "4250.000000",
      "reference": "INV-2291"
    }
  },
  "request": { "id": "req_8kL3mQ2xNp", "idempotency_key": "order:ord_8812:intent" }
}

Event catalogue#

EventResourceNotes
payment_intent.processingPayment intentSeen onchain, not final.
payment_intent.succeededPayment intentTerminal.
payment_intent.partially_paidPayment intentOutside tolerance.
payment_intent.expiredPayment intentTerminal.
payment_intent.revertedPayment intentReorg removed a prior success.
payment_intent.unsupported_chainPayment intentNo credit applied.
payout.createdPayoutAccepted.
payout.leg.broadcastPayout legReached the network.
payout.settledPayoutTerminal.
payout.partially_settledPayoutTerminal, partial.
payout.failedPayoutTerminal.
payout.batch.completedBatchAll legs terminal.
swap.executingSwapBroadcast.
swap.settledSwapCredited.
swap.failedSwapNot completed.
limit_order.workingLimit orderWatching venues.
limit_order.partially_filledLimit orderTranche filled.
limit_order.filledLimit orderFully filled.
limit_order.canceledLimit orderCancelled or expired.
balance.updatedBalanceAny change; includes as_of.
recipient.screening_updatedRecipientScreening verdict changed.

Retries and response handling#

Deliveries are attempted for up to 24 hours with exponential backoff. Any 2xx acknowledges the event. 410 Gone disables the endpoint permanently.

ts
export async function POST(request: Request) {
  const raw = await request.text();
  const event = juicepay.webhooks.constructEvent(
    raw,
    request.headers.get("juicepay-signature")!,
    process.env.JUICEPAY_WEBHOOK_SECRET!,
  );

  await enqueue(event); // fast, durable
  return Response.json({ received: true });
}

Replay#

bash
curl -s -X POST "$JUICEPAY_URL/v1/webhook_endpoints/we_5mK2/replay" \
  -H "Authorization: Bearer $JUICEPAY_KEY" \
  -d '{ "from": "2026-08-18T00:00:00Z", "to": "2026-08-19T00:00:00Z" }'

Replayed events keep their original IDs, so a consumer that deduplicates on event ID will correctly treat them as already processed.

Delivery log#

bash
curl -s "$JUICEPAY_URL/v1/webhook_endpoints/we_5mK2/deliveries?limit=20" \
  -H "Authorization: Bearer $JUICEPAY_KEY"

Each entry records the response status, latency, and attempt number — enough to diagnose a misbehaving consumer without guessing.