Loopwise Docs
Webhooks

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:

  1. Validate the Request: Ensure the request is from a trusted source.
  2. Parse the Data: Decode the JSON data and process it according to your business logic. For example, updating order status or recording transaction details.
  3. 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.27

Webhook 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.

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:

  1. Extract the signature from the Loopwise-Webhook-Signature header.
  2. Compute the expected signature using the payload and your signing key.
  3. 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:

Ruby
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."
end
Node.js
const 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 }, and data.id identifies 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 typeRetry delays after the initial attemptTotal attempts (incl. initial)
Server errors (5xx), 408, 429; connection timeouts, refused/reset, host/network unreachable; transient TLS handshake failures1 min, 5 min, 30 min, 2 h5
Other client errors (4xx); DNS resolution failures; TLS certificate-verification failures1 min2

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:

  • *.created events are scheduled within roughly 0–59 seconds of the change.
  • *.updated events are scheduled within roughly 60–299 seconds of the change, always after the corresponding created event.

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.

On this page