Skip to main content
Webhooks are how you hear that a proposal was accepted or an eForm was submitted. Polling list endpoints will not tell you those moments reliably, and it will burn your rate limit.

1. Ask which events exist

GET /api/v1/webhooks/events returns a JSON array of event keys, for example proposal.accepted and eform.submitted. Subscribe only to keys in that list. The list is the contract. Do not hard-code a key you have not seen there. proposal.accepted does not fire when you send the proposal. eform.submitted does not fire when you send the form.

2. Subscribe once per event

POST /api/v1/webhooks needs write access. One row is one event. To receive both events at the same URL, create two subscriptions.
hookUrl is your HTTPS endpoint. targetUrl is accepted as an alias for Zapier-style callers. Send one of them. webhookSecret is optional. Omit it and OnboardMe generates one. The 201 returns id and webhookSecret once. Store the secret in your own vault before you respond 200 to your user. A later list of subscriptions does not show it again. If you lose it, unsubscribe and subscribe again.

3. Verify every delivery

OnboardMe POSTs JSON to hookUrl and sets: The signature is HMAC-SHA256 of the string:
rawBody is the exact bytes you received, before JSON parsing and before any framework re-serialises the object. A parsed-and-reprinted body will not match.
Reject anything that fails the comparison. Then reject timestamps outside a few minutes of your clock so a captured body cannot be replayed later. Compare in UTC.

4. Dedupe, then do the work

Store X-Om-Event-Id. If you have already processed that id, return 200 and stop. OnboardMe retries when your endpoint is slow or returns an error, and a retry is the same event id with the same body. Return 2xx quickly. Download PDFs, create bills, or call back into the API after you have persisted the event id, or from a queue. Doing that work inside the request is how you time out and get a second delivery. The JSON body is the delivery for that event. Camel-case fields match the REST API. proposal.accepted (schemaVersion 4): eform.submitted (schemaVersion 1): Do not treat either body as a full proposal or a full form definition. Load the REST resource when you need line items, files, or status that the event does not include.

5. Prove the endpoint, then remove it

POST /api/v1/webhooks/{id}/test sends a signed test delivery to that subscription. Verify your HMAC code against the test before you go live. The test is not a real proposal.accepted. DELETE /api/v1/webhooks/{id} removes the subscription. Use the id from the 201, not the event name.

Production checklist

  • HTTPS only, and a URL you control. A request-bin left over from development will receive client names.
  • One secret per subscription, stored outside source control.
  • Signature checked before you parse the body for business logic.
  • X-Om-Event-Id unique index so retries are harmless.
  • Handlers for the events you subscribed to, and a default that records and ignores anything else so a new event key cannot crash the endpoint.

Webhook reference

Events, subscribe, unsubscribe, and the test delivery.