Skip to main content
This walkthrough implements a production-grade webhook receiver for Orgo events. It covers subscription, idempotent handling, fast responses, and replay. Who is this for: any integration that needs to react to changes in Orgo in near-real-time (CRM sync, member onboarding automation, payment reconciliation, contract-signed notifications).

Available events

Eighteen event types, across six entity families. Subscribe to any subset.

Step 1 — Subscribe

Pick the events you care about and an HTTPS endpoint you control.
The secret is yours to choose — a 32-byte random string is standard. Orgo uses it to sign each delivery so you can verify the payload came from Orgo. Response includes the subscription ID — store it; you’ll need it for delivery-log lookups.

Step 2 — Receive the delivery

Each event is delivered as an HTTPS POST with a JSON body and these headers: Body shape (Stripe-inspired):
For *.updated events, previous_attributes carries the diff — fields not mentioned didn’t change. This is enough to detect specific transitions (“user just unsubscribed”, “payment just succeeded”) without comparing to a stored snapshot.

Step 3 — Verify the signature

Do this before you look at the body. The endpoint is public, so anything that reaches it is unauthenticated until the signature says otherwise. X-Webhook-Signature is t=<unix seconds>,v1=<hex>, where v1 is HMAC-SHA256(secret, "<t>." + raw request body). The t is the same value as X-Webhook-Timestamp, and it is inside the signed string, so a captured delivery cannot be re-stamped and replayed.
Three rules the code above is built around:
  • Hash the raw bytes. request.get_data(), not request.get_json() re-serialized. Key order and whitespace differ after a round trip, and the MAC changes with them.
  • Compare in constant time. hmac.compare_digest, crypto.timingSafeEqual, hash_equals, never ==.
  • Do not deduplicate on the signature. Retries are re-signed at send time, so the same delivery ID arrives with a different v1. Dedupe on the payload id, as in Step 4.
Returning 401 marks the delivery failed and Orgo retries it, which is what you want when the cause is a secret you have just rotated on one side only. A JavaScript version of the same function is in the webhooks concept page.
This format shipped in August 2026, replacing a short non-keyed checksum. There is no compatibility header: a receiver comparing the old value must move to the recipe above.

Step 4 — Be idempotent

Orgo may deliver the same event twice — retries on transient failures, network races on at-least-once delivery. Your handler must be safe to call repeatedly with the same payload. The simplest pattern: a processed_deliveries table keyed by id (the top-level event ID, e.g. wh_evt_67aa128c2f4a):
Insert the row inside the same transaction as the side effect, so a crash mid-way doesn’t leave the row inserted without the effect. If the effect is in a different system (CRM API call), insert the row only after the effect succeeds.

Step 5 — Respond fast

Orgo waits up to 30 seconds for a response before treating the delivery as failed. Long-running work (sending a follow-up email, querying a slow downstream API) should go into a background queue. The webhook handler’s job is to acknowledge receipt and durably queue the work — not perform it.
Return any 2xx; Orgo doesn’t care about the body.

Step 6 — Test before going live

The test endpoint fires a synthetic event of your chosen type to the subscription URL:
The synthetic payload uses fixture data (not real user data) but carries the same headers and shape your handler will see in production. Use it in CI to validate end-to-end delivery.

Step 7 — Replay failed deliveries

Every delivery attempt — success or failure — is logged. To list:
For each failed delivery, the response includes the original payload — you can re-process it locally without needing Orgo to re-deliver. Logs are retained for ~90 days.

Retry behavior

The subscription itself is not automatically deactivated on repeated failures — your monitoring should watch for high failure rates and either auto-pause or page on-call.

Common gotchas

Orgo doesn’t guarantee delivery order across event types. The created timestamp on each event lets you reconstruct order — sort by created before processing if order matters. For per-resource ordering (“two updates to the same user”), use previous_attributes to detect skipped transitions.
Always route by tenant_id in the payload, not by the subscription ID. If a single CRM serves multiple Orgo tenants, each tenant should create its own subscription, and your receiver should route incoming events to the right CRM tenant based on payload['tenant_id'].
Not currently — list explicit event types in the events array. The full list is in the Webhooks concept page. Subscribing to events that don’t yet exist is harmless but won’t catch new types if Orgo adds them — patch your subscription when new events ship.

What to do next