Gareth Wilson Gareth Wilson

Guide to Aurora Solar Webhooks: Features and Best Practices

Published


Aurora Solar webhooks tell your systems what is happening in your Aurora tenant: a project is created or sold, a design is ready, an AutoDesigner or performance simulation job finishes, an agreement is signed. If you sell or design solar installations on Aurora Solar, webhooks keep your CRM and post-sales workflows in step with Aurora without polling its API for status changes.

This guide covers how Aurora Solar's URL-template webhooks work, the events you can subscribe to, how to authenticate requests that Aurora doesn't sign, and the practices that keep a receiver reliable.

What are Aurora Solar webhooks?

Aurora Solar webhooks look different from most. There is no JSON body. Aurora says its webhooks "are fired as GET requests to a URL generated using a template provided by you", and the data travels in that URL.

A webhook subscription names one event and a URL template. The template contains placeholders for the event's attributes, and Aurora fills them in when the event fires. This template:

https://www.yourapp.com/auto_designer_job_completed?design_id=<DESIGN_ID>&job_id=<JOB_ID>&status=<STATUS>

produces requests like this:

GET https://www.yourapp.com/auto_designer_job_completed?design_id=3fa85f64-5717-4562-b3fc-2c963f66afa6&job_id=5cf99a21-5717-4562-b3fc-2c963f66abc6&status=succeeded

Aurora deliberately sends only "proprietary identifiers and statuses". To get the project, design or job itself, call the Aurora API with the IDs from the query string.

Requests are not signed. Aurora offers two optional ways to authenticate them: a header token, where you choose a header name and value, or Basic Auth.

Aurora Solar webhook features

FeatureDetails
ConfigurationThe webhooks dashboard, or POST /tenants/{tenant_id}/webhooks
Subscription modelOne event per subscription, at most 5 subscriptions per event per tenant
MethodGET, with no body
PayloadQuery parameters from your URL template: identifiers and statuses only
Event nameNot sent, unless your template adds it as a static parameter
FiltersAllowed values per URL attribute, such as STATUS of succeeded
AuthenticationOptional: header token (header_auth_key, header_auth_value) or Basic Auth
SignatureNone
Response window10 seconds
Retries5 more attempts over about 24 hours, on a timeout or a 3xx, 4xx or 5xx
Auto-disableWhen all retries fail within 48 hours, with at least 50 attempts
OrderingNot guaranteed
Source IPsNot published. Aurora suggests a DNS allowlist on *.aurorasolar.com

Common events

Aurora documents 32 events. Each one lists the URL attributes you can put in the template and the filters it supports.

AreaEventsTypical attributes
Projectsproject_created, project_changed, project_status_changed, project_deleted<PROJECT_ID>, <SOURCE>, <STATUS>, <ATTRIBUTES>
Designsdesign_created, design_deleted, asset_created<PROJECT_ID>, <DESIGN_ID>, <ASSET_TYPE>
Design requestsdesign_request_created, design_request_completed, design_request_rejected<DESIGN_REQUEST_ID>, <STATUS>
Jobsauto_designer_job_completed, performance_simulation_job_completed, ai_roof_job_completed, proposal_pdf_generation_job_completed and others<DESIGN_ID>, <JOB_ID>, <STATUS>, <SOURCE>
Salesmilestone_created, milestone_shared, agreement_status_changed, web_proposal_url_created, web_proposal_viewed<STAGE>, <AGREEMENT_ID>, <IS_FIRST_VIEW>
Otherimagery_lidar_completed, ahj_lookup_completed, order_created, site_survey_requested, utility_bill_upload_job_completed and moreVaries by event

A few details catch people out:

  • Several job events, including ai_roof_job_completed, proposal_pdf_generation_job_completed and fire_pathway_placement_job_completed, fire only for jobs started through the API, not from the Aurora app.
  • project_changed fires only when one of a listed set of attributes changes, such as status, owner_id or customer_email, and <ATTRIBUTES> is a comma-separated list of what changed.
  • A project status "can be any string", unlike the fixed statuses of other events.
  • On milestone_created, <CHANGE_TYPES> is empty unless <STAGE> is change_order. Aurora warns that a CHANGE_TYPES filter without a STAGE=change_order filter stops every other milestone from arriving.
  • Order IDs are 24-character hex ObjectIds, not UUIDs.

Setting up Aurora Solar webhooks

Because each request carries only what your template asks for, design the template first. Add a static parameter that names the event, because nothing else in the request does. Aurora's own example uses ?aurora_event=design_request_rejected.

  1. Pick a header name and a long random value, for example X-Aurora-Webhook-Token and the output of openssl rand -hex 32. Store the value server-side.
  2. Create a subscription in the Aurora webhooks dashboard, or through the API, with the event, an HTTPS URL template and the header token:
POST /tenants/{tenant_id}/webhooks

{
  "webhook": {
    "description": "Project created",
    "event": "project_created",
    "url_template": "https://example.com/webhooks/aurora-solar?aurora_event=project_created&project_id=<PROJECT_ID>&source=<SOURCE>",
    "enabled": true,
    "header_auth_key": "X-Aurora-Webhook-Token",
    "header_auth_value": "your-long-random-value"
  }
}
  1. Optionally add filters so Aurora only fires for the values you care about, such as succeeded jobs.
  2. Repeat for each event you need.

With Hookdeck. Aurora Solar is a supported source in Hookdeck. Create a source with the Aurora Solar type, enter the header name as the API Key Header and the header value as the API Key, and use the Hookdeck source URL as the start of your URL template, followed by your query parameters. Hookdeck's setup note says to set header_auth_key and header_auth_value on the Aurora webhook and enter them on the source, because webhooks without them send nothing to verify. Basic Auth subscriptions are not covered by this source type, so use the header token. The source pre-selects GET, since that is the method Aurora uses. The method isn't managed, so keep GET in the source's allowed methods. A request without the right header still gets a 200 from Hookdeck, but Hookdeck marks it as failed verification and doesn't deliver it to your service.

For local development, hookdeck listen 3000 aurora-solar --path /webhooks/aurora-solar from the Hookdeck CLI gives you a public HTTPS URL that forwards to your local server.

Securing Aurora Solar webhooks

Check the header before you read the query string. Look the header up case-insensitively, compare the whole value in constant time, and check lengths before calling crypto.timingSafeEqual, which throws on buffers of different lengths.

const crypto = require('crypto');
const express = require('express');

function safeEqual(a, b) {
  const x = Buffer.from(String(a), 'utf8');
  const y = Buffer.from(String(b), 'utf8');
  return x.length === y.length && crypto.timingSafeEqual(x, y);
}

function verifyAuroraRequest(headers) {
  const secret = process.env.AURORA_SOLAR_WEBHOOK_SECRET; // your header_auth_value
  if (!secret) return false; // fail closed when unconfigured
  const name = (process.env.AURORA_SOLAR_WEBHOOK_HEADER || 'X-Aurora-Webhook-Token').toLowerCase();
  const received = headers[name];
  return typeof received === 'string' && safeEqual(received, secret);
}

const app = express();

// Aurora sends GET with no body. express.raw() still reads any body a proxy adds.
app.get('/webhooks/aurora-solar', express.raw({ type: '*/*' }), (req, res) => {
  if (!verifyAuroraRequest(req.headers)) {
    return res.sendStatus(401);
  }

  const { aurora_event, project_id, design_id, job_id, status } = req.query;
  if (typeof aurora_event !== 'string') return res.sendStatus(400);

  // Queue the IDs, then fetch the full objects from the Aurora API asynchronously.
  res.sendStatus(200);
});

The same check in Python:

import hmac
import os


def verify_aurora_request(headers) -> bool:
    secret = os.environ.get("AURORA_SOLAR_WEBHOOK_SECRET")
    if not secret:
        return False
    name = os.environ.get("AURORA_SOLAR_WEBHOOK_HEADER", "X-Aurora-Webhook-Token")
    received = headers.get(name)  # case-insensitive in Starlette and Flask
    if not received:
        return False
    return hmac.compare_digest(received.encode("utf-8"), secret.encode("utf-8"))

A static header proves the sender knows the value, but it doesn't prove the query string is unchanged, and it gives no replay protection: nothing is signed and Aurora sends no timestamp. Use an HTTPS URL, so the query string and the header are encrypted in transit, keep the value long and random, and keep it out of logs. Because the payload is only IDs and statuses, re-reading the object from the Aurora API before acting also limits what a forged request can do.

Make Aurora Solar webhooks production-ready. Hookdeck Event Gateway checks the header token on every Aurora request, stores each one with its query string, and retries delivery to your service, so a long outage doesn't get your subscription auto-disabled.

Aurora Solar webhook limitations and pain points

The request doesn't say which event it is

The Problem: A handler that receives several Aurora events on one URL can't tell them apart.

Why It Happens: Each subscription is for one event, and Aurora sends only the attributes your template includes. There is no event header and no body.

Workarounds:

  • Put a static parameter such as aurora_event=project_created in every template.
  • Or give each event its own path, such as /webhooks/aurora-solar/project_created.

How Hookdeck Can Help: Hookdeck filters can match on the query string, so one Aurora Solar source can route aurora_event=project_created to your CRM sync and job events to your design pipeline.

No signature, and the credential is optional

The Problem: A subscription created without a header token sends requests that anyone could imitate.

Why It Happens: Aurora's authentication is optional, and both of its options are static credentials.

Workarounds:

  • Always set header_auth_key and header_auth_value.
  • Treat each webhook as a prompt to read the object from the Aurora API, not as the source of truth.

How Hookdeck Can Help: Hookdeck's Aurora Solar source compares the header you name against the exact value you enter, and logs every request with its headers and query string.

A failing endpoint gets switched off

The Problem: After a long enough outage, Aurora stops sending a subscription's events until someone re-enables it.

Why It Happens: Aurora retries a failed webhook 5 more times, at exponentially increasing intervals over 24 hours: Aurora's table lists approximate intervals of 30 seconds, 5 minutes, 30 minutes, 3 hours and 20 hours. If all retries for a subscription fail within 48 hours, and it has had at least 50 attempts, Aurora disables it.

Workarounds:

  • Return 200 within 10 seconds and do slow work afterwards.
  • Re-enable the subscription in the dashboard or through the Webhooks API once the failure is fixed.
  • For events you missed entirely, Aurora can resend notifications within 30 days of the original event through your account representative.

How Hookdeck Can Help: Hookdeck accepts each request as it arrives and stores it, then delivers to your service with automatic retries on a schedule you control. Your own downtime doesn't count against Aurora's limit, and Issues alert you when deliveries fail.

Out-of-order and repeated webhooks

The Problem: An agreement shows as viewed after it was signed, or the same job completion arrives twice.

Why It Happens: Aurora says ordering "is not guaranteed", and that it may occasionally send the same webhook twice.

Workarounds:

  • Fetch the current state from the API rather than applying statuses in arrival order.
  • Deduplicate on identifiers in the query string, such as job_id or design_request_id, as Aurora suggests.

How Hookdeck Can Help: Hookdeck deduplication can drop repeated requests before they reach your handler.

Best practices

Template only what you need

Each attribute in the template is another query parameter to parse and log. Include the IDs you need to call the API, the status if you filter on it, and the static event name.

Use filters to cut noise

Filters run in Aurora, before anything is sent. A performance_simulation_job_completed subscription filtered to STATUS of succeeded only fires for jobs worth acting on.

Rotate the header value carefully

Aurora says each retry "will use the most recent webhook definition so that errors in URLs or credentials can be corrected". After you change header_auth_value, even retries of older events carry the new value, so update your receiver at the same time.

Acknowledge fast and process asynchronously

Check the header, queue the IDs, return 200, then call the Aurora API. See why to process webhooks asynchronously.

Make handlers idempotent

Key on the event name and the IDs in the query string, and update rather than insert. See our guide to webhook idempotency.

Conclusion

Aurora Solar sends each webhook as an unsigned GET whose query string holds the identifiers and statuses your URL template asks for. Add a static event name to every template, set a header token and compare it in constant time over HTTPS, fetch full objects from the API, and answer within 10 seconds so a subscription is never auto-disabled.

Hookdeck Event Gateway accepts Aurora's GET requests, checks the header token, routes on the query string and holds each event until your service has processed it.

Get started with Hookdeck for free and handle Aurora Solar webhooks reliably in minutes.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.