Subscribing
Create a subscription with a POST to /webhooks, scoped to a portfolio. You provide a primary URL (must be HTTPS), an optional secondary URL for Enterprise mirroring, an array of event types, and a description. We return a subscription ID and a per-portfolio signing secret. The secret is shown once — store it before the response window closes.
curl -X POST https://api.suiteprofit.org/v2/webhooks \
-H "Authorization: Bearer sp_live_portfolio_47b91cb3..." \
-H "X-SuiteProfit-Portfolio: prt_motlawa_estates" \
-H "Content-Type: application/json" \
-d '{
"url": "https://ops.motlawa-estates.example/webhooks/suiteprofit",
"mirror_url": "https://siem.motlawa-estates.example/webhooks/mirror",
"events": ["rate.decision.published", "parity.breach.detected", "fiscal.batch.approved"],
"description": "Portfolio ops pipeline"
}' Event types
| Event | Description |
|---|---|
rate.decision.published | A governed rate decision was published and written to Profitroom Suite across one or more properties. |
rate.decision.reverted | A previously published rate decision was reverted; audit trail records the reason and approver. |
guest.thread.escalated | A cross-property guest conversation crossed the escalation threshold for portfolio ops. |
reputation.score.dropped | Portfolio-level reputation score dropped past the configured brand ceiling. |
parity.breach.detected | OTA parity breach detected against the approved rate floor for a property. |
fiscal.batch.approved | A fiscal batch (KSeF / FA(2)) was approved by the finance operator and submitted upstream. |
operations.task.escalated | An operations task escalated past its SLA tier; routed to the on-call rota. |
sso.user.provisioned | A user was provisioned into the portfolio via SCIM or SAML JIT. |
sso.user.deprovisioned | A user was deprovisioned; all per-property tokens issued to that principal are revoked. |
Payload shape
Every payload is JSON. The top-level envelope is stable across event types and always carries the portfolio ID:
{
"id": "evt_2b7d1c8a19f4",
"type": "rate.decision.published",
"created_at": "2026-07-28T09:12:44Z",
"delivery_id": "del_a92c81",
"portfolio_id": "prt_motlawa_estates",
"properties": ["prop_gdansk_marina", "prop_sopot_grand"],
"data": {
"decision_id": "rdc_a19f4b7d",
"room_type_code": "STDD",
"date_range": { "from": "2026-08-14", "to": "2026-08-21" },
"approver_saml_subject": "revenue.director@motlawa.example",
"brand_ceiling_id": "brk_marina_flag"
}
} HMAC signature
Every delivery is signed with HMAC-SHA256 over the raw request body, using the per-portfolio signing secret. The signature ships in X-SuiteProfit-Signature as a hex string, alongside X-SuiteProfit-Portfolio. Verify by recomputing HMAC on the raw body — not the parsed JSON — and comparing constant-time.
// Node example
const crypto = require('crypto');
const expected = crypto
.createHmac('sha256', process.env.SP_WEBHOOK_SECRET)
.update(rawBody)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sigHeader))) {
return res.status(401).end();
} Delivery SLA and retry policy
At-least-once. A delivery is considered successful when your endpoint returns HTTP 2xx within 15 seconds. Failures retry with exponential backoff over 24 hours: at 1 min, 5 min, 15 min, 1h, 4h, 12h, 24h. Enterprise plans carry a 99.95% delivery SLA measured on the primary URL and receive SLA credit when the SLA is missed by more than 0.05%. Failed events land in a dead-letter queue that operators can replay for up to 90 days.
Enterprise webhook mirroring
Enterprise plans can register a mirror_url that receives an identical copy of every delivery. The mirror URL is typically pointed at a SIEM ingest endpoint (Splunk HEC, Elastic, or an S3+SQS bridge) and joins the enterprise audit stream. Mirror failures do not affect primary delivery accounting; the primary URL is the SLA-bearing target.
Per-endpoint throughput
We will not deliver more than 80 events per second per primary URL on Enterprise (20/s on Group, 5/s on Chain Starter). Excess is queued rather than dropped. Portfolios that need more can split events across multiple subscriptions with disjoint event filters.
Testing
Every subscription supports a POST /webhooks/{id}/ping that emits a synthetic webhook.ping event. Use it to verify signing and mirror wiring before enabling production events.