RevKeen Docs
WebhooksEvents

invoice.paid

Fires when an invoice is fully settled

Fires when an invoice's status transitions to paid. This is the canonical entry point for post-billing fulfilment on recurring plans.

invoice.paid and payment.succeeded are two views of the same money movement. Choose one based on your handler:

  • Digital fulfilmentinvoice.paid (post-tax, post-discount amount is on the invoice)
  • Ledger reconciliationpayment.succeeded (gateway-level truth)

If you act on both, deduplicate by invoice.id to avoid double-fulfilling.

Payload

{
  "id": "evt_1a2b3c4d5e6f",
  "type": "invoice.paid",
  "created": 1705689600,
  "livemode": true,
  "data": {
    "object": {
      "id": "inv_01HK4X7Z2M5N8P0Q3R6S9T2V5",
      "object": "invoice",
      "number": "INV-2026-00042",
      "status": "paid",
      "customer_id": "cus_01HK4X7Z2M5N8P0Q3R6S9T2V5",
      "subscription_id": "sub_01HK4X7Z2M5N8P0Q3R6S9T2V5",
      "amount_due_minor": 9999,
      "amount_paid_minor": 9999,
      "amount_remaining_minor": 0,
      "currency": "USD",
      "due_date": "2026-01-31",
      "paid_at": "2026-01-19T12:00:00Z",
      "lines": [
        {
          "id": "il_01HK4X7Z2M5N8P0Q3R6S9T2V5",
          "description": "Professional plan — January 2026",
          "quantity": 1,
          "unit_amount_minor": 9999,
          "subtotal_minor": 9999
        }
      ],
      "metadata": {}
    },
    "previous_attributes": {
      "status": "open",
      "amount_paid_minor": 0,
      "amount_remaining_minor": 9999
    }
  }
}

Handler contract

  1. Verify the signature.
  2. Deduplicate by event.id.
  3. If subscription_id is set → refresh the subscription's service entitlement window.
  4. If the invoice has digital-delivery line items → run delivery.
  5. Send a receipt if your billing doesn't already do that (RevKeen's dispatch system handles receipts by default — opt out with automatic_receipt: false on the subscription).
  6. Return 2xx.

previous_attributes

For update-style events, data.previous_attributes lists what changed. For invoice.paid, expect:

{
  "status": "open",
  "amount_paid_minor": 0,
  "amount_remaining_minor": 9999
}

Do not rely on this for state reconstruction — always read the current state from data.object.

On this page