Webhooks · Coming in Phase 1

Real-time events

Don't poll. Subscribe to the events you care about and Tenaro pushes them to your HTTPS endpoint the moment they happen, signed, retried, and replayable.

How it works

Register an endpoint

Webhook subscriptions are managed per workspace. Self-serve subscription UI in the dashboard is coming with Phase 1. Today, contact [email protected] and we'll set one up for you. You'll get a unique signing secret per endpoint.

Receive POSTs

Every event arrives as a JSON POST to your URL within seconds:

POST /your/webhook-endpoint HTTP/1.1
Content-Type: application/json
X-Tenaro-Event: payment.succeeded
X-Tenaro-Delivery: whd_9f2ac41be077d311
X-Tenaro-Signature: t=1735689600,v1=5d41402abc4b2a76b9719d911017c592…

{
  "id": "evt_9f2ac41be077d311",
  "type": "payment.succeeded",
  "created": 1735689600,
  "workspace": "yourworkspace",
  "data": {
    "payment_id": "pay_123",
    "amount_cents": 185000,
    "tenant_id": "tnt_456"
  }
}

Return 2xx fast

Acknowledge within 10 seconds. Do heavy work asynchronously. We retry anything else.

Verifying signatures

Every delivery is HMAC-SHA256 signed with your endpoint's secret. Always verify. Never trust an unsigned POST.

import hmac, hashlib

def verify(payload: bytes, header: str, secret: str) -> bool:
    # header: "t=1735689600,v1=5d4140…"
    parts = dict(p.split("=", 1) for p in header.split(","))
    signed = parts["t"].encode() + b"." + payload
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])
🕒

Check the timestamp

Reject deliveries where t is more than 5 minutes old, because it defeats replay attacks.

🔁

Idempotency

We may redeliver. Key your processing on the event id (evt_…) and dedupe.

📡

Retries

Non-2xx or timeouts retry with exponential backoff over ~24 hours, then the delivery is marked failed and visible in your dashboard.

Events catalog

The v1 event set. Every event carries id, type, created (unix), workspace, and data.

Leases

EventDescription
lease.createdA lease was created.
lease.renewedA lease was renewed.
lease.terminatedA lease was terminated.
lease.expiring_soonA lease enters its renewal window (configurable, default 60 days).

Payments

EventDescription
payment.succeededA payment completed successfully.
payment.failedA payment attempt failed. Includes the failure reason.
payment.refundedA payment was refunded, fully or partially.
invoice.overdueAn invoice passed its due date unpaid.
charge.createdAn ad-hoc charge (fee, utility bill-back) was created.

Maintenance

EventDescription
maintenance.createdA maintenance request was submitted.
maintenance.status_changedA request moved between statuses. Includes old and new status.
maintenance.completedWork was marked complete.
work_order.assignedA vendor was assigned to a job.
work_order.completedA vendor marked their job complete.

People & documents

EventDescription
tenant.invitedA tenant was invited to the portal.
tenant.moved_inA tenant's lease started.
tenant.moved_outA tenant's lease ended.
document.uploadedA document was uploaded.
document.signedA document was e-signed by all parties.
user.deactivatedA user was deactivated.
user.role_changedA user's role changed. Includes old and new role.

Get events flowing

Webhook subscriptions open with Phase 1. Tell us which events you need and we'll set up your endpoint.

Request webhook access