Webhooks

Receive real-time notifications for payment events, tenant screening results, and other platform activities.

Overview

DoorStax dispatches webhooks for key events from integrated payment and screening providers. Webhooks are sent as HTTP POST requests with JSON payloads to your configured endpoint.

Kadima Payment Events

Payment events are dispatched by the Kadima payment processor and forwarded to your webhook endpoint.

EventDescription
transaction.completedA payment has been successfully processed
transaction.failedA payment attempt has failed
transaction.refundedA payment has been refunded to the payer
recurring.processedA recurring payment has been automatically processed

Signature Verification

All webhook payloads are signed using HMAC-SHA256. The signature is sent in the x-kadima-signature header. Always verify signatures before processing webhooks.

Verify Webhook Signature (Node.js)
import crypto from "crypto";

function verifyWebhookSignature(payload, signature, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(payload)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// In your webhook handler:
app.post("/webhooks/kadima", (req, res) => {
  const signature = req.headers["x-kadima-signature"];
  const isValid = verifyWebhookSignature(
    JSON.stringify(req.body),
    signature,
    process.env.KADIMA_WEBHOOK_SECRET
  );

  if (!isValid) {
    return res.status(401).json({ error: "Invalid signature" });
  }

  // Process the webhook event
  const { event, data } = req.body;
  // ...

  res.status(200).json({ received: true });
});

Example Payloads

transaction.completed

transaction.completed
{
  "event": "transaction.completed",
  "timestamp": "2025-01-15T14:30:00.000Z",
  "data": {
    "transactionId": "txn_abc123",
    "amount": 150000,
    "currency": "USD",
    "status": "completed",
    "paymentMethod": "ach",
    "tenant": {
      "id": "tnt_012",
      "email": "tenant@example.com"
    },
    "lease": {
      "id": "lea_xyz789",
      "unitId": "unt_456"
    },
    "metadata": {
      "description": "Rent payment - January 2025"
    }
  }
}

transaction.failed

transaction.failed
{
  "event": "transaction.failed",
  "timestamp": "2025-01-15T14:30:00.000Z",
  "data": {
    "transactionId": "txn_def456",
    "amount": 150000,
    "currency": "USD",
    "status": "failed",
    "failureReason": "insufficient_funds",
    "failureCode": "R01",
    "paymentMethod": "ach",
    "tenant": {
      "id": "tnt_012",
      "email": "tenant@example.com"
    },
    "retryEligible": true,
    "nextRetryAt": "2025-01-18T14:30:00.000Z"
  }
}

transaction.refunded

transaction.refunded
{
  "event": "transaction.refunded",
  "timestamp": "2025-01-20T10:00:00.000Z",
  "data": {
    "transactionId": "txn_abc123",
    "refundId": "ref_ghi789",
    "amount": 150000,
    "currency": "USD",
    "status": "refunded",
    "reason": "duplicate_payment",
    "initiatedBy": "usr_pm001"
  }
}

recurring.processed

recurring.processed
{
  "event": "recurring.processed",
  "timestamp": "2025-02-01T06:00:00.000Z",
  "data": {
    "transactionId": "txn_rec001",
    "recurringId": "rec_abc123",
    "amount": 150000,
    "currency": "USD",
    "status": "completed",
    "paymentMethod": "ach",
    "schedule": {
      "frequency": "monthly",
      "dayOfMonth": 1,
      "nextDate": "2025-03-01"
    },
    "tenant": {
      "id": "tnt_012",
      "email": "tenant@example.com"
    }
  }
}

RentSpree Screening Webhooks

DoorStax integrates with RentSpree for tenant background screening. Screening results are delivered via webhooks when the screening is complete.

EventDescription
screening.completedScreening report is ready for review
screening.pendingScreening has been initiated and is in progress
screening.failedScreening could not be completed

Retry Policy

If your endpoint returns a non-2xx status code or times out (30 seconds), DoorStax will retry the webhook delivery with exponential backoff:

AttemptDelay
1st retry1 minute
2nd retry5 minutes
3rd retry30 minutes
4th retry2 hours
5th retry (final)24 hours

Best Practice: Always return a 200 status code quickly and process the webhook asynchronously. This prevents timeouts and ensures reliable delivery.