Receive signed operational alerts
Partner alert delivery turns customer health and human-waiting states into a closed, machine-readable event stream. Payloads contain ids, enums, counts, timestamps, and author-controlled resource names - never exception messages or operator notes.
Verify before processing
Signed deliveries carry:
X-Yekar-Timestamp: 1786687200
X-Yekar-Key-Id: pak_1
X-Yekar-Signature: sha256=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
The signature above is an example, not a real signature. Compute lowercase hex HMAC-SHA256 over <timestamp>.<exact raw request body> using the secret associated with X-Yekar-Key-Id, then compare in constant time. Reject stale timestamps before parsing or acting on the body.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyPartnerAlert(rawBody: Buffer, timestamp: string, signatureHeader: string, secret: string) {
const claimed = Buffer.from(signatureHeader.replace(/^sha256=/, ""), "hex");
const expected = Buffer.from(
createHmac("sha256", secret).update(`${timestamp}.`).update(rawBody).digest("hex"),
"hex",
);
return claimed.length === expected.length && timingSafeEqual(claimed, expected);
}
Delivery behavior
Your endpoint should return a successful HTTP response only after durably accepting the event. Transient failures are retried with bounded backoff; delivery health distinguishes a retryable failure, exhausted delivery, and an undeliverable event with no live destination.
Every webhook body contains a unique deliveryId. Dedupe on that value before applying an effect. The sender suppresses terminal ledger re-entry, but a network failure after your server commits and before the response arrives can still make the remote outcome ambiguous.
Event types
The closed event set is:
org.budget.thresholdautomation.failure_rate.breachedautomation.failure_rate.recoveredrun.human_task.expiredrun.gate.pendingrun.human_task.pendingrun.approval.pendingrun.approval.expiredorg.support_session.endedautomation.stuck.breachedautomation.stuck.recoveredorg.subscription.trial_endingorg.subscription.ending_soonorg.subscription.endedorg.subscription.reactivatedorg.subscription.charge_failedorg.subscription.mandate_haltedorg.subscription.mandate_cancelledorg.credit.thresholdorg.suspendedorg.reactivated
The stuck pair carries automationId, automationName and stuckCount. The subscription events carry the plan's planName and the date that matters: trialEndsAt with daysLeft; endsAt with daysLeft and a reason of cancellation, term_end, auto_renew or payment_due; endedAt with a reason of canceled or expired; or periodEndsAt for reactivation and the three payment events. org.credit.threshold is sent once per allowance period, while that period's credit is live, when the credit on us left - unused monthly credit rolls over, so it can include earlier periods' - falls to 100 - threshold percent of one period's allowance or less. It carries threshold, usedUsd (spent this period), allowanceUsd (one period's credit on us), remainingUsd (credit on us left), creditExpiresAt (when trial or one-time credit expires; null for monthly credit, which rolls over), creditExpiryKind (trial for a plan trial's credit, one_time for one-time credit; set exactly when creditExpiresAt is) and periodEndsAt. remainingUsd, creditExpiresAt and creditExpiryKind were added later and are absent from an event queued before them. org.suspended is sent when Yekar.AI suspends the organization and carries suspendedAt and a cause of billing, terms, security, requested or other; org.reactivated carries reactivatedAt and a cause of operator or deletion_cancelled. A suspension because the plan ended is org.subscription.ended instead.
Every payload carries orgId, blocking, and expiresAt. Event-specific fields add bounded facts such as threshold counts, agent or flow identity, a gate/task/tool identifier, or support-session impact counts. This no-free-text contract is a security guarantee: downstream logs and queues cannot accidentally ingest exception messages or operator-written text from the alert body.
For org.support_session.ended, use grantId with the authenticated support-session read when you need the stated reason.