Why webhooks
For a typical partner integration, webhooks are strictly better than polling on three dimensions:- Freshness. A webhook delivers within seconds of the source event. A polled sync runs on whatever interval you configure — typically minutes or hours.
- Cost. Webhook deliveries do not count against your hourly rate limit. A poll that scans for recent changes does — even when nothing has changed.
- Completeness. Webhooks fire for events generated by any source — your own integration, other integrations, manual entry by a Virtuous user, the nightly Transaction batch. A poll only sees changes that landed before your last query timestamp.
How the flow works
The full lifecycle of a webhook integration: The four pieces:- Subscribe. Your integration calls
POST /api/Webhookto register a payload URL and select which events you want delivered. Virtuous returns the subscription details, including thesecretused for signature verification. - Receive. When a subscribed event occurs in the nonprofit’s Virtuous organization, Virtuous sends an HTTP POST to your
payloadUrlwith the event payload. - Verify and process. Your endpoint verifies the request signature (proving the request came from Virtuous and was not tampered with), then processes the event.
- Acknowledge. Your endpoint returns a
2xxHTTP status to confirm delivery. Non-2xxresponses trigger retries — see Retry Behavior.
Subscribing to webhooks
A webhook subscription specifies (a) where to deliver events and (b) which events to deliver. Create one withPOST /api/Webhook:
id for later management (updating event selections, deactivating, deleting). Store the secret securely — it is used to verify the authenticity of every incoming webhook delivery.
The subscription fields
| Field | Type | Description |
|---|---|---|
id | integer | The webhook subscription’s primary key. Assigned at creation. |
payloadUrl | string | The HTTPS URL Virtuous will POST events to. Must accept POST requests with a JSON body. |
secret | string | A shared secret used for signature verification. You provide this on subscription creation. Keep it in a secrets manager. |
active | boolean | When true, Virtuous delivers events to this subscription. When false, events are not delivered (and not queued for later — they are dropped). |
contactCreate / contactUpdate | boolean | Subscribe to Contact lifecycle events. |
giftCreate / giftUpdate / giftDelete | boolean | Subscribe to Gift lifecycle events. |
projectCreate / projectUpdate / projectDelete | boolean | Subscribe to Project lifecycle events. |
formSubmission | boolean | Subscribe to form submission events from Virtuous-hosted forms. |
contactNoteCreate / contactNoteUpdate / contactNoteDelete | boolean | Subscribe to ContactNote lifecycle events. |
eventCreate / eventUpdate / eventDelete | boolean | Subscribe to Event (calendar/program event) lifecycle events. |
Generating the secret
You provide thesecret value when creating the subscription. Virtuous does not generate it for you. Use a cryptographically secure random string of at least 32 bytes:
Managing webhook subscriptions
The standard CRUD endpoints work as expected:| Endpoint | Use |
|---|---|
GET /api/Webhook/{webhookId} | Retrieve a single subscription by ID. |
POST /api/Webhook | Create a new subscription. |
PUT /api/Webhook/{webhookId} | Update a subscription — change the payload URL, rotate the secret, or modify the event selections. |
PUT /api/Webhook/{webhookId}/Active | Toggle the active flag in a single call. Pass ?active=true or ?active=false as a query parameter. |
DELETE /api/Webhook/{webhookId} | Permanently delete the subscription. |
Updating the event selections
To subscribe to additional event types after the initial creation, send aPUT /api/Webhook/{webhookId} with the full set of fields you want enabled:
cURL
GET-then-PUT pattern used elsewhere in CRM+ minimizes the risk of inadvertently disabling event types you previously subscribed to.
Rotating the secret
To rotate the webhook secret, generate a new value andPUT the subscription with the new secret. The new secret takes effect on the next webhook delivery. Your endpoint must accept both the old and new secret during the rotation window — at minimum the time between the API call and your verification code being updated.
A safer rotation pattern: maintain a list of valid current and recent secrets in your verifier, accept signatures matching any of them, and drop the old secret from the list once you have confirmation that no deliveries with the old signature have arrived.
Endpoint requirements
YourpayloadUrl must meet a few requirements to receive webhooks reliably:
| Requirement | Why |
|---|---|
| HTTPS only | Virtuous does not deliver to plain HTTP URLs. Use a valid TLS certificate signed by a public certificate authority. |
| Accept POST with JSON body | All webhook deliveries are HTTP POST with Content-Type: application/json. |
Return 2xx on success | Any 2xx status (most commonly 200 or 204) acknowledges receipt. Non-2xx triggers retries. |
| Respond quickly | Industry-standard webhook timeouts are 10–30 seconds. Slow endpoints risk timeout-triggered retries even when processing succeeds. Defer heavy processing to a background queue. |
| Idempotent processing | Retries can deliver the same event multiple times. Your handler must produce the same result if invoked twice with the same payload. See Idempotency and Safe Reprocessing. |
The exact delivery timeout enforced by Virtuous is not documented in the OpenAPI spec.⚠️ Human input required: Confirm the exact delivery timeout (seconds before Virtuous considers a delivery failed and triggers retry) so partners can size their endpoints appropriately.
The receiver pattern
The recommended shape of a webhook receiver:JavaScript
- Capture the raw body. Signature verification runs over the exact bytes Virtuous sent. JSON-parsing-then-re-serializing changes whitespace and ordering, which breaks the signature.
- Acknowledge before processing. Return
2xxas soon as you have verified and stored the event. Heavy processing in the request handler increases the risk of timeout-triggered retries. - Queue the work. A robust integration pushes the event onto a background queue (SQS, Cloud Tasks, Redis Stream, etc.) and processes asynchronously. This isolates the webhook endpoint’s latency from the cost of downstream processing.
Where to go next
Event Types
The 15 event types CRM+ delivers and what each one represents.
Signature Verification
How to verify that an incoming webhook came from Virtuous and was not tampered with.
Retry Behavior
What happens when your endpoint is unavailable or returns non-2xx.
Idempotency and Safe Reprocessing
Why and how to make your handler safe against duplicate deliveries.
Local Testing
Receive Virtuous webhooks on localhost using a tunneling tool.
Rate Limits
Why webhooks are the recommended pattern when the alternative is polling.