Webhooks
In this guide, we will look at how to register and consume webhooks to integrate your app with Loopwise.
In this guide, we will look at how to register and consume webhooks to integrate your app with Loopwise. With webhooks, your app can know when something happens in Loopwise, such as a payment being completed or a user enrolling in a course.
Integration Guide
Webhooks provide an efficient way for your system to receive real-time notifications about events from our platform. This includes scenarios such as when an order is placed, when a refund occurs, or when a new user registers.
Handling Webhooks
Once your endpoint receives a webhook request, you should:
- Validate the Request: Ensure the request is from a trusted source.
- Parse the Data: Decode the JSON data and process it according to your business logic. For example, updating order status or recording transaction details.
- Respond to the Request: Respond to the webhook request. Typically, a HTTP status code 200 indicates successful receipt.
IP Allowlisting
To enhance security and ensure that webhook requests are only received from legitimate sources, we recommend setting up IP allowlisting for your webhook endpoints. This ensures that only requests from Loopwise's trusted IP addresses are accepted.
Loopwise Webhook IP Address
Please add the following IP address to your allowlist for incoming webhook requests:
52.194.91.27Webhook Signing and Verification
To ensure the security and integrity of webhook payloads sent from Loopwise, we implement a signing mechanism. This section explains how to use and verify the signature.
Signature
Each webhook request sent from Loopwise includes a signature in the header. This signature is computed using the payload and a secret signing key.
Header
The signature is included in the Loopwise-Webhook-Signature header of the HTTP request.
For backward compatibility during a deprecation window, the same signature is
also sent under the legacy Teachify-Webhook-Signature header. New
integrations should read Loopwise-Webhook-Signature; the legacy header will
be removed in a future release.
Verifying the Signature
To verify that a webhook request genuinely came from Loopwise and wasn't tampered with, you should:
- Extract the signature from the
Loopwise-Webhook-Signatureheader. - Compute the expected signature using the payload and your signing key.
- Compare the computed signature with the one in the header.
Signature Computation
The signature is computed using HMAC SHA-256. Here are examples of how to compute and verify the signature:
require 'openssl'
def verify_signature(payload, signature, signing_key)
computed_signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), signing_key, payload)
computed_signature == signature
end
# Usage
payload = request.body.read # Get the raw payload
signature = request.headers['Loopwise-Webhook-Signature']
signing_key = 'your_signing_key_here'
if verify_signature(payload, signature, signing_key)
puts "Signature verified. Process the webhook."
else
puts "Invalid signature. Reject the webhook."
endconst crypto = require("crypto");
function verifySignature(payload, signature, signingKey) {
const computedSignature = crypto
.createHmac("sha256", signingKey)
.update(payload)
.digest("hex");
return computedSignature === signature;
}
// Usage
// Assuming you have middleware to access raw body
const payload = req.rawBody;
const signature = req.headers["loopwise-webhook-signature"];
const signingKey = "your_signing_key_here";
if (verifySignature(payload, signature, signingKey)) {
console.log("Signature verified. Process the webhook.");
} else {
console.log("Invalid signature. Reject the webhook.");
}Replace 'your_signing_key_here' with the actual signing key provided by Loopwise.
Security Considerations
- Keep your signing key secret. Do not expose it in client-side code or public repositories.
- Always verify the signature before processing webhook payloads.
- Use HTTPS for all webhook endpoints to ensure the security of data in transit.
- The signature covers only the raw request body. It does not include a timestamp or a per-delivery identifier, so there is no built-in replay protection. The payload is
{ type, data }, anddata.ididentifies the affected resource, not a unique delivery — the same value recurs across every update to that resource, so it cannot distinguish a replay from a genuine later event. Make handlers idempotent by reconciling against the resource's current state (re-fetch it, or compare with your stored copy) rather than by deduplicating on an id.
By implementing this verification process, you can ensure that the webhooks you receive are genuine and haven't been tampered with, enhancing the security of your integration with Loopwise.
Delivery, Retries, and Limits
Each event is delivered as an HTTP POST with a JSON body. Your endpoint should respond with a 2xx status code as quickly as possible. Each automatically scheduled request has a 30-second timeout, and a request that exceeds it is treated as a failed delivery. The timeout is per request, so a delivery that follows redirects (up to four hops) can run longer than 30 seconds overall.
Retry Schedule
If a delivery fails, Loopwise retries with a fixed backoff based on the failure type:
| Failure type | Retry delays after the initial attempt | Total attempts (incl. initial) |
|---|---|---|
Server errors (5xx), 408, 429; connection timeouts, refused/reset, host/network unreachable; transient TLS handshake failures | 1 min, 5 min, 30 min, 2 h | 5 |
Other client errors (4xx); DNS resolution failures; TLS certificate-verification failures | 1 min | 2 |
Some failures are terminal and are not retried: requests blocked by our SSRF protection and redirect loops. After the final attempt the delivery is marked failed and recorded in your webhook logs.
Deliveries can arrive out of order and, on retry, more than once. Since the payload carries no unique per-delivery id, make your handler idempotent by reconciling against the resource's current state rather than by deduplicating on an id.
Endpoint Limit
Each school can have at most 5 active webhook endpoints. Every active endpoint subscribed to an event receives its own delivery.
Event Timing
Events are scheduled shortly after the underlying change is committed, with a scheduling delay derived from the commit time (the current time reduced to a fixed window). The delay is not randomized, so changes committed at the same instant can share the same delay:
*.createdevents are scheduled within roughly 0–59 seconds of the change.*.updatedevents are scheduled within roughly 60–299 seconds of the change, always after the correspondingcreatedevent.
These are scheduling delays, not delivery-time guarantees. Each subscribed endpoint is then staggered by a further ~5 seconds per endpoint (up to ~20 seconds for the fifth), and queue and network latency apply on top, so do not assume an event arrives within those windows.
user.updated
user.updated fires on any committed change to a user record that runs the model's Active Record callbacks, not only on profile edits you initiate. Writes that bypass callbacks — such as sign-in timestamp updates made with update_columns — do not emit it, so you cannot treat the event as a log of every change. Do not assume a specific field changed either — compare against your own stored copy, or re-fetch the user, before acting on it.