Aggregated Events#

CasaPay sends webhook events for all aggregated collection lifecycle changes. Subscribe to these events to track tenant onboarding, payment processing, and collection results.

Event types#

EventDescription
payment_agreement.createdA new PaymentAgreement was created
payment_agreement.activatedTenant completed onboarding, agreement is active
payment_agreement.suspendedAgreement suspended (method failed, mandate cancelled)
payment_agreement.cancelledAgreement was cancelled
payment_request.createdA new PaymentRequest was created
payment_request.processingCasaPay is actively collecting payment
payment_request.succeededPayment collected successfully
payment_request.failedAll collection attempts exhausted
payment_request.retryingCollection attempt failed, retry scheduled

payment_agreement.created#

Triggered when a new PaymentAgreement is created.

{
  "id": "evt_pa_created_001",
  "object": "event",
  "type": "payment_agreement.created",
  "data": {
    "object": {
      "id": "pa_abc123",
      "object": "payment_agreement",
      "tenant": "cus_123456789",
      "entity": "ent_456def",
      "status": "pending_onboarding",
      "onboarding_url": "https://pay.casapay.com/onboard/pa_abc123",
      "payment_methods_available": ["card", "open_banking", "direct_debit"],
      "created": 1706140800
    }
  },
  "created": 1706140800,
  "livemode": true
}

payment_agreement.activated#

Triggered when a tenant completes onboarding and the agreement becomes active.

{
  "id": "evt_pa_activated_001",
  "object": "event",
  "type": "payment_agreement.activated",
  "data": {
    "object": {
      "id": "pa_abc123",
      "object": "payment_agreement",
      "tenant": "cus_123456789",
      "entity": "ent_456def",
      "status": "active",
      "preferred_method": "direct_debit",
      "mandate": "md_xyz789",
      "created": 1706140800
    }
  },
  "created": 1706227200,
  "livemode": true
}

payment_agreement.suspended#

Triggered when an agreement is suspended due to a permanent method failure.

{
  "id": "evt_pa_suspended_001",
  "object": "event",
  "type": "payment_agreement.suspended",
  "data": {
    "object": {
      "id": "pa_abc123",
      "object": "payment_agreement",
      "status": "suspended",
      "preferred_method": "direct_debit",
      "mandate": "md_xyz789"
    }
  },
  "created": 1709251200,
  "livemode": true
}

payment_agreement.cancelled#

Triggered when a PaymentAgreement is cancelled.

{
  "id": "evt_pa_cancelled_001",
  "object": "event",
  "type": "payment_agreement.cancelled",
  "data": {
    "object": {
      "id": "pa_abc123",
      "object": "payment_agreement",
      "status": "cancelled"
    }
  },
  "created": 1709337600,
  "livemode": true
}

payment_request.created#

Triggered when a new PaymentRequest is created.

{
  "id": "evt_preq_created_001",
  "object": "event",
  "type": "payment_request.created",
  "data": {
    "object": {
      "id": "preq_abc123",
      "object": "payment_request",
      "payment_agreement": "pa_abc123",
      "amount": 120000,
      "currency": "eur",
      "description": "Rent — March 2025",
      "status": "pending",
      "created": 1709251200
    }
  },
  "created": 1709251200,
  "livemode": true
}

payment_request.processing#

Triggered when CasaPay begins actively collecting the payment.

{
  "id": "evt_preq_processing_001",
  "object": "event",
  "type": "payment_request.processing",
  "data": {
    "object": {
      "id": "preq_abc123",
      "object": "payment_request",
      "payment_agreement": "pa_abc123",
      "amount": 120000,
      "currency": "eur",
      "status": "processing",
      "payment_method_used": "direct_debit",
      "attempts": 1
    }
  },
  "created": 1709251260,
  "livemode": true
}

payment_request.succeeded#

Triggered when payment is collected successfully.

{
  "id": "evt_preq_succeeded_001",
  "object": "event",
  "type": "payment_request.succeeded",
  "data": {
    "object": {
      "id": "preq_abc123",
      "object": "payment_request",
      "payment_agreement": "pa_abc123",
      "amount": 120000,
      "currency": "eur",
      "status": "succeeded",
      "payment_method_used": "direct_debit",
      "attempts": 1,
      "metadata": {
        "property_id": "prop_456",
        "period": "2025-03"
      }
    }
  },
  "created": 1709337600,
  "livemode": true
}

payment_request.failed#

Triggered when all collection attempts are exhausted and the payment was not collected.

{
  "id": "evt_preq_failed_001",
  "object": "event",
  "type": "payment_request.failed",
  "data": {
    "object": {
      "id": "preq_def456",
      "object": "payment_request",
      "payment_agreement": "pa_def456",
      "amount": 95000,
      "currency": "eur",
      "status": "failed",
      "payment_method_used": "card",
      "failure_reason": "insufficient_funds",
      "attempts": 3
    }
  },
  "created": 1709596800,
  "livemode": true
}

Failure reasons#

CodeDescription
insufficient_fundsTenant's account has insufficient funds
card_declinedCard was declined by the issuer
mandate_cancelledDirect debit mandate has been cancelled
account_closedTenant's bank account has been closed
authentication_requiredPayment requires additional authentication
expired_methodPayment method has expired

payment_request.retrying#

Triggered when a collection attempt fails but a retry is scheduled.

{
  "id": "evt_preq_retrying_001",
  "object": "event",
  "type": "payment_request.retrying",
  "data": {
    "object": {
      "id": "preq_def456",
      "object": "payment_request",
      "payment_agreement": "pa_def456",
      "amount": 95000,
      "currency": "eur",
      "status": "processing",
      "payment_method_used": "direct_debit",
      "failure_reason": "insufficient_funds",
      "attempts": 1,
      "next_retry_at": 1709337600
    }
  },
  "created": 1709251200,
  "livemode": true
}

Handling aggregated events#

app.post('/webhooks/casapay', async (req, res) => {
  const event = req.body;

  switch (event.type) {
    case 'payment_agreement.activated':
      await markTenantOnboarded(event.data.object.tenant);
      break;
    case 'payment_agreement.suspended':
      await notifyOperator(event.data.object);
      break;
    case 'payment_request.succeeded':
      const request = event.data.object;
      await markRentPaid(request.metadata.property_id, request.metadata.period);
      break;
    case 'payment_request.failed':
      await escalateFailedPayment(event.data.object);
      break;
    case 'payment_request.retrying':
      await logRetryAttempt(event.data.object);
      break;
  }

  res.status(200).send();
});