Skip to content

Webhooks

Instead of polling, receive a signed POST to your endpoint when something happens.

Set your sandbox endpoint yourself on the account page. It must be HTTPS. Saving one issues a signing secret, shown once. For production endpoints, email support@roofangle.com.

Events

EventWhen
order.createdAfter an order is placed
order.payment_completedAfter Stripe payment clears (v1 only)
order.updatedRoofAngle changed the order details, payload carries each changed field with its old and new value
order.deliveredFiles are ready, payload includes downloadAllUrl (24-hour token)
order.cancelledOrder was cancelled

Orders are read-only over the API: you create them, RoofAngle fulfils them. When RoofAngle changes an order, order.updated tells you what changed rather than you polling for it. It reports detail changes such as propertyType, pdfRequired, serviceId, address or price, not internal workflow steps. In the sandbox this fires automatically a couple of minutes after you place an order, see how sandbox orders progress.

Request headers

HeaderMeaning
X-Webhook-EventEvent name
X-Webhook-TimestampUnix seconds
X-Webhook-Signaturesha256= HMAC-SHA256 of "{timestamp}.{body}" with your shared secret
X-Webhook-Delivery-IdStable across retries, use for deduplication
X-Webhook-Attempt1-4

Verifying the signature

Always verify before trusting the payload:

js
import crypto from 'node:crypto';

function verify(req, secret) {
  const ts = req.headers['x-webhook-timestamp'];
  const sig = req.headers['x-webhook-signature'];
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(`${ts}.${req.rawBody}`)
    .digest('hex');
  return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}
csharp
bool Verify(string ts, string body, string sig, string secret)
{
    using var h = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
    var expected = "sha256=" + Convert.ToHexString(
        h.ComputeHash(Encoding.UTF8.GetBytes($"{ts}.{body}"))).ToLower();
    return CryptographicOperations.FixedTimeEquals(
        Encoding.UTF8.GetBytes(sig), Encoding.UTF8.GetBytes(expected));
}

Delivery & retries

  • Respond 2xx within 5 seconds, and do heavy work asynchronously.
  • Non-2xx responses are retried at 1s, 5s, 30s backoff (3 retries after the first attempt).
  • Use X-Webhook-Delivery-Id to deduplicate: retries carry the same id.
  • Reject requests whose timestamp is older than a few minutes to prevent replay.

Need an API key? Contact support@roofangle.com