Application Webhooks
Application webhooks are HTTP calls that Riseact sends to your endpoint when certain events occur in all the organizations that have installed your application.
Unlike organization webhooks, you register them only once on the partner app. Every call carries the domain of the organization that generated the event.
Setting up a webhook
- In the partner portal open Apps and select your application.
- Open the Webhooks tab and click Add Webhook.
- Choose the events and enter the HTTPS URL that will receive the
POSTcalls. - Save.
From the same tab you edit or delete registered webhooks.
Available events
| Event | Description |
|---|---|
supporter.created | A new supporter has been created |
supporter.updated | A supporter has been updated |
supporter.deleted | A supporter has been deleted |
checkout.created | A new checkout has been opened |
checkout.updated | A checkout has been updated |
checkout.paid | A checkout has been completed with payment |
checkout.closed | A checkout has been closed |
donation.created | A new donation has been created |
donation.updated | A donation has been updated |
donation.deleted | A donation has been deleted |
donation.revoked | A donation has been revoked: by hand, by the refund of its last payment, or by the failed payment of a one-off donation |
payment.created | A new payment has been recorded |
payment.updated | A payment has been updated |
campaign.created | A new campaign has been created |
campaign.updated | A campaign has been updated |
campaign.deleted | A campaign has been deleted |
Call delivery
Riseact sends a POST with Content-Type: application/json to the registered URL. The payload is the same as for organization webhooks:
organization: the domain of the organization that generated the event, to route it;event: the event topic, for exampledonation.created;object: the affected object;idempotency_key: identifier of the call. The same key can arrive more than once: use it to deduplicate.
Example (donation.created):
{
"organization": "org-che-ha-installato-app",
"object": {
"id": 229,
"code": "#1000",
"state": "pending",
"amount": 10,
"frequency": 0,
"campaign": 29,
"supporter": 75835,
"payment_method": "MANUAL"
},
"event": "donation.created",
"idempotency_key": "b1e0c9a2-4f3d-4e2a-9c1b-6a7f8d0e2c34"
}
Examples for the other objects are on the organization webhooks page.
- The timeout is 10 seconds: respond immediately with
2xxand process heavy work asynchronously. - If the answer is not
2xx, or does not arrive within the timeout, Riseact retries the delivery after 1 minute, 10 minutes, 1 hour, 6 hours and 24 hours, with the same payload and the sameidempotency_key. A retried call may therefore arrive after newer events for the same object: compareupdate_datewith the one you stored and drop outdated payloads. - Calls are signed with the app's client secret: always verify the signature before processing the payload (see Verifying the signature).
Verifying the signature
Calls of the webhooks registered by an app are signed with the app's client secret, so you can verify they come from Riseact. The same holds for the uninstall call. The request carries two headers:
X-Riseact-Timestamp: when the call was sent, in Unix seconds.X-Riseact-Signature:v1=followed by the hex encoded HMAC-SHA256 of the string<timestamp>.<body>.<body>is the raw bytes received, before any JSON parsing.
The v1= prefix identifies the signature scheme. In the future the header may carry several comma separated signatures (for instance while the secret is rotated): accept the request if at least one v1 signature matches.
To verify it:
- Compute the HMAC with the client secret over the bytes you received and compare it with the signature using a constant-time comparison.
- Reject the request if the timestamp is more than 5 minutes away from the current time, and deduplicate on
idempotency_key: together they stop an intercepted call from being replayed.
The Node.js SDK verifies it for you. Otherwise, in Node.js:
const crypto = require("crypto");
function verifyRiseactSignature(rawBody, headers, clientSecret) {
const timestamp = headers["x-riseact-timestamp"];
if (!timestamp || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const expected = crypto
.createHmac("sha256", clientSecret)
.update(`${timestamp}.`)
.update(rawBody)
.digest();
return (headers["x-riseact-signature"] || "")
.split(",")
.map((part) => part.trim().split("="))
.some(([version, hex]) => {
if (version !== "v1" || !/^[0-9a-f]{64}$/.test(hex)) return false;
return crypto.timingSafeEqual(Buffer.from(hex, "hex"), expected);
});
}
rawBody must be the bytes you received, not re-serialized JSON: with Express use the verify option of express.json() to keep them, or express.raw({ type: "application/json" }) on the webhook route.
Webhooks registered from the partner portal are signed. Webhooks an app registers on a single organization through the API, with its own token, are signed too. A webhook created before signing was introduced stays unsigned until the app updates it or registers it again.
App uninstall
When an organization uninstalls your app, or is deleted, Riseact revokes the app's tokens and sends a signed POST to a fixed path:
<app_url>/webhooks/app-uninstalled
<app_url> is the app URL set in the partner portal. There is nothing to register: the call is not listed among the app's webhooks and cannot be turned off. Use it to delete the data and credentials you keep for that organization.
{
"organization": "org-that-uninstalled",
"event": "app.uninstalled",
"object": {
"id": 42,
"client_id": "<your app client id>",
"organization": "org-that-uninstalled",
"reason": "app_uninstalled",
"uninstalled_at": "2026-10-14T12:00:00.000000+00:00"
},
"idempotency_key": "6f1d2c3b-8a9e-4f70-b1c2-d3e4f5a6b7c8"
}
reasonisapp_uninstalledwhen the organization uninstalled the app,organization_deletedwhen the organization was deleted.- Always verify the signature before deleting anything.
- Answer
2xxonly once the deletion succeeded, so that on failure Riseact retries as for the other webhooks. - The call may arrive more than once, and your app may have already deleted the data: handle it idempotently.
- When the call arrives the tokens are already revoked, so you can no longer query the API for that organization.
- Private apps of a deleted organization are deleted with it, and they do not get the call.
With the Node.js SDK the path is handled by the SDK: implement auth.onUninstall to delete your data, credentials are removed automatically.
Event log
The app's Events tab lists every delivery attempt, with organization, event, payload, HTTP status (or connection error) and response body.
A failed delivery can be retried manually from the event detail.
Difference from organization webhooks
| Application webhooks | Organization webhooks | |
|---|---|---|
| Scope | All the orgs that install the app | A single organization |
| Where they are registered | Partner portal, Webhooks tab | Organization admin, private app |
| Who uses them | Third-party partner apps | Internal automations and integrations |
| Payload | Same (includes organization) | Same (includes organization) |