Skip to main content

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​

  1. In the partner portal open Apps and select your application.
  2. Open the Webhooks tab and click Add Webhook.
  3. Choose the events and enter the HTTPS URL that will receive the POST calls.
  4. Save.

From the same tab you edit or delete registered webhooks.

Available events​

EventDescription
supporter.createdA new supporter has been created
supporter.updatedA supporter has been updated
supporter.deletedA supporter has been deleted
checkout.createdA new checkout has been opened
checkout.updatedA checkout has been updated
checkout.paidA checkout has been completed with payment
checkout.closedA checkout has been closed
donation.createdA new donation has been created
donation.updatedA donation has been updated
donation.deletedA donation has been deleted
donation.revokedA donation has been revoked: by hand, by the refund of its last payment, or by the failed payment of a one-off donation
payment.createdA new payment has been recorded
payment.updatedA payment has been updated
campaign.createdA new campaign has been created
campaign.updatedA campaign has been updated
campaign.deletedA 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 example donation.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 2xx and 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 same idempotency_key. A retried call may therefore arrive after newer events for the same object: compare update_date with 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:

  1. Compute the HMAC with the client secret over the bytes you received and compare it with the signature using a constant-time comparison.
  2. 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"
}
  • reason is app_uninstalled when the organization uninstalled the app, organization_deleted when the organization was deleted.
  • Always verify the signature before deleting anything.
  • Answer 2xx only 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 webhooksOrganization webhooks
ScopeAll the orgs that install the appA single organization
Where they are registeredPartner portal, Webhooks tabOrganization admin, private app
Who uses themThird-party partner appsInternal automations and integrations
PayloadSame (includes organization)Same (includes organization)