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
| Event | When |
|---|---|
order.created | After an order is placed |
order.payment_completed | After Stripe payment clears (v1 only) |
order.updated | RoofAngle changed the order details, payload carries each changed field with its old and new value |
order.delivered | Files are ready, payload includes downloadAllUrl (24-hour token) |
order.cancelled | Order 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
| Header | Meaning |
|---|---|
X-Webhook-Event | Event name |
X-Webhook-Timestamp | Unix seconds |
X-Webhook-Signature | sha256= HMAC-SHA256 of "{timestamp}.{body}" with your shared secret |
X-Webhook-Delivery-Id | Stable across retries, use for deduplication |
X-Webhook-Attempt | 1-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-Idto deduplicate: retries carry the same id. - Reject requests whose timestamp is older than a few minutes to prevent replay.
