# Light API Documentation > Documentation: https://docs.light.dev > API Reference: https://docs.light.dev/api-reference > OpenAPI Schema: https://api.light.dev/v1/openapi.json > Website: https://www.poweredbylight.com ## Overview Light provides API and infrastructure for companies to offer branded electricity plans without managing operational complexity. This documentation covers the Light API, authentication, enrollment flows, billing, and integration guides. ## Contents This file contains the complete Light API documentation for use by AI assistants and other automated tools. For the full OpenAPI specification, see https://api.light.dev/v1/openapi.json --- # enrollment > https://docs.light.dev/guides/enrollment # Enrollment ## Intro Enrollment is where customers sign up for your power plan, typically in a linear flow. Behind the scenes, the flow attaches a [`Location`](/key-concepts#entities) entity to an [`Account`](/key-concepts#entities) entity. The enrollment experience can be completely customized by using our API with your UI. Light also provides an optional, low-code embedded flow ([prebuilt UI](/prebuilt-ui#embedded-flows)) that can be embedded into your website or mobile app. The embedded flow includes a full enrollment experience that appears in a modal overlaying your experience. The embedded flow can be a great way to get started with a branded and complete enrollment flow embedded within your existing UI. If you want a more customized experience, we recommend using the API. Both the API and embedded flow require you to have an app token and create a customer account prior to enrolling it. Refer to the first two steps in [our quickstart](/quickstart) to learn how to do that. ### Prerequisites - An account or invite to the [Light dashboard](https://dashboard.light.dev/) - An existing web or native mobile app to add the enrollment experience to - If not, see [our example app](https://github.com/light-technology/example-app) ## Embedded flow [image] The embedded flow lets you skip all of the UI work needed to create an enrollment flow. Only app token authentication and not account tokens is needed for this implementation. Use the `enrollment` scope to surface this specific experience. [Learn more](/prebuilt-ui#tutorial) [Our quickstart](/quickstart) is a much more in-depth tutorial for implementing the enrollment flow in a pre-built UI. ### Pre-filling address information If you already have an address for your user and are using an embedded flow, you can set the `preliminary_address` field when creating the `Account`. This will be used as a suggestion when the user is searching for their address. If the location is eligible, then we see around 90% of these addresses end up matching one we have on file. If there is a match we will skip the address search step, while allowing the user to optionally correct the address. The `preliminary_address` field is essentially passed to the full address search API described lower in the API section. That API does some normalization to the address to try and find an exact match to an address on file with the utility. ## API Using an API implementation creates a convinent user experience where you can embed the steps within your existing onboarding flows and skip UI steps for customer data you already have. ### 1. (optional) Filter to eligible postal codes Not every address is eligible for electricity retail choice. So if you already have address information for your users, it can be useful to filter ahead of time to only show the enrollment flow to users that are likely to be eligible. First, filter to only states where you're offering a retail electricity product. The next step we recommend is using our Eligibility API [`/v1/app/accounts/enroll/eligibility`](/api-reference#tag/app-enrollment/post/v1/app/accounts/enroll/eligibility). This API accepts either a full address or at least a `postal_code` to determine eligibility of a specific location. The response will return a payload like: ``` { "eligibility_likelihood": 0, "utilities": [ { "display_name": "ONCOR", "name": "ONCOR" }, { "display_name": "CenterPoint", "name": "CENTERPOINT" } ] } ``` The `eligibility_likelihood` here indicates a percentage between 0-100 if the location is eligible in a retail choice electricity plan. We recommend anything above `0` continuing past this check, but anything `0` you could show a screen indicating that their address is not in a retail choice area. ### 2. Identify the `Location` Not every address is eligible for electricity retail choice, so it is important to be able to search and match the user's address to a valid utility service address registered with the utility. In Texas, valid addresses are identified by their ESI ID, a unique identifier designated by ERCOT. #### Full address search If you already have the address for the customer on file, you can attempt to map it to an address on file with the Utility by using this API: [`/v1/app/accounts/enroll/full-address-search`](/api-reference#tag/app-enrollment/POST/v1/app/accounts/enroll/full-address-search). This API will only return results that appear to be exact matches for the location provided. The API will attempt to do some address normalization in case the address you have uses `123 Main Street Apt 1` and the utility address is `123 Main St #1`, it will still show matches. However it will not fuzzy-match mispellings or incorrect numbers like `124 Main St` or `123 Mian St`. We find that this often has 90+% accuracy with matching eligible addresses, and we are improving it over time to be even higher. If there is exactly one result returned then it is most likely the correct address for the customer. If you get 0 results, then you may want to offer the type-ahead search or allow the customer to enter their ESIID from a previous utility bill as a fallback. The type-ahead API can help a customer sometimes correct these on their own if their street has multiple names or other common alternative names. It is possible to sometimes receive multiple results if there is more than one electricity meter for the location. #### Type-ahead search by address [image] The [`/account/enroll/address-search`](/api-reference#tag/account/POST/v1/account/enroll/address-search) API is best to use if the customer's address is unknown or an exact match was not found. It is meant to be used as a type-ahead search where the user can start typing the beginning of their address and see results. This API helps you map an address to its corresponding ESI ID by returning a list of addresses (and their ESI IDs) which match the search query. The search uses a fuzzy search algorithm to match the query to the address even if the query is not an exact match. The exact formatting of these addresses may not match that in other data sources like Google Maps since they come directly from the Utilities we work with. This endpoint is provided as a convenience. However, because it uses fuzzy search and certain addresses can be very similar, it is critical that customers review and confirm the selection of their full address in a separate step and not rely on the ordering of the results from this API. #### Search by Electric Service Identifier ID (ESI ID) [image] You can also identify a Location directly by ESI ID. This is a useful fallback to [`/account/enroll/address-search`](/api-reference#tag/account/POST/v1/account/enroll/address-search) when the user has their ESI ID available and wants to enroll in a plan using that ESI ID. They can usually find their ESI ID on a previous electricity bill. You can pass the ESIID collected to [`/v1/account/enroll/esi-id-search`](/api-reference#tag/enrollment/POST/v1/account/enroll/esi-id-search), which is useful for validating the ESI ID and returning the address for the user to confirm. We recommend providing this as a fallback for when the address search does not return any results. This can sometimes be the case if the format of the addresses correlated with the ESI ID is sufficiently different from the address the user is searching for. ### 3. Confirm the address All of the methods above help to identify the ESI ID for an address. The ESI ID is the unique identifier used by utilities that is needed to enroll a specific electric meter. Once you have determined ESI ID from one of the search mechanisms above, the user must confirm the address details before proceeding with the enrollment process to prevent faulty or delayed enrollments. The utility account number (ESI ID) should also be shown to the user to confirm that they are enrolling the correct service location. The addresses returned by our APIs will likely look slightly different from those in your system, so even if you already had the correct address for the user make sure you are showing them the address returned by our APIs to confirm. Here is an example of what an integration might show a customer using that address response from the search above. ### 4. Request available plans [image] After the service location address is confirmed, the next step is to request available plans for the address. This can be done by using the [`/account/enroll/plans/request`](/api-reference#tag/account/POST/v1/account/plans/request) API. This API will return a list of available plans for the address in which the user can enroll. The plans contain details about all of the sub-components that make up the plan. This includes the energy rate, delivery (TDU) rates, monthly plan charges, term length, and links to the Electricity Facts Label (EFL) and Terms of Service (TOS) documents. The user can review the plans and enroll in them. The response will also include PDF document links provided for your plans and must be linked to from your app for the user to review as needed. The plan details and variables that are contained within the EFL and TOS documents are also available in the API response for displaying some of the key components to customers in your app (for example the energy rate and term length). You can also request plans without an ESI ID by calling [`/v1/app/accounts/enroll/plans/request`](https://docs.light.dev/api-reference#tag/app-enrollment/POST/v1/app/accounts/enroll/plans/request). This API also allows features like customizing the rates per-account. ### 5. Accept the plan [image] After the user has reviewed a plan, they can chose to accept the plan and continue in the enrollment process. This can be done by using the [`/account/enroll/plans/accept`](/api-reference#tag/account/POST/v1/account/plans/accept) API. In the visual example above, this would be equivalent to the user clicking the button at the bottom of the screen to accept the plan. When calling this API, the `terms_accepted` field must be set to true to indicate that the user has accepted the terms of the plan. The `start_date` field must be set to a date within the range of the `earliest_start_date` and `latest_start_date` fields from the plan details. Calling this API will attach the ESI ID to the Account, creating a Location with a Contract including the terms of the accepted plan. However, the enrollment won't be finalized until the rest of the Enrollments steps below are finalized. ### 6. Verify identity As part of the enrollment process, users must verify their identity to ensure compliance with regulatory requirements. This step helps protect against fraud and ensures the account is being set up for the correct individual. The minimum information required to start service includes first and last name, phone number, email, and date of birth. Set these fields when you create a user's account using `POST /app/accounts`, or collect it later and send via `PATCH /account`. Once you have set these critical fields, confirm you are ready to check the customer's identity and supply additional information using `POST /account/enroll/identity`. The more information provided, the stronger the likelihood of successful identity verification. The actual identity verification process won't start until after the enrollment is submitted. ### 7. Check credit By default, we will perform a required soft credit check to determine your customer's eligibility. The credit check requires the user's full Social Security Number (SSN) and explicit consent to the terms of the credit check. Use `POST /account/enroll/credit-check` to submit the SSN to be used for credit check. The credit check will be processed in the background once the enrollment is submitted. If the account doesn't pass the credit check with SSN, then our support team will reach out directly to the customer via email for alternative credit verification methods. ### 8. Add a payment method [image] If you are using our built-in Stripe integration, you can access a Stripe public key in our [`/account`](/api-reference#tag/account/GET/v1/account) payload (`app.stripe_public_key`) to use in the various [Stripe Checkout](https://docs.stripe.com/payments/checkout) or [Stripe Elements](https://stripe.com/docs/stripe-js) components that work with your frontend stack. If you are working in the sandbox environment, then this public key will be a Stripe test key with which you can use the standard Stripe test card numbers. You can also save a copy of the public key elsewhere or cache it for future use. It isn't likely to change frequently but may change over the years if needed for security reasons. In order collect a payment method from the customer, you will first need to create a payment session using the [`POST /account/billing/payment-session`](/api-reference#tag/account/POST/v1/account/payment-session) API. This API will return a client secret for this account that you can use to create a payment method using [Stripe Elements](https://docs.stripe.com/payments/accept-a-payment?platform=web&ui=elements#add-and-configure-the-elements-provider-to-your-payment-page) along with the Stripe public key. After the payment method is added to Stripe, you will need to confirm with Light by using the [`POST /account/billing/ensure-payment-method-added`](/api-reference#tag/account/POST/v1/account/payment-method) API. This API simply takes the `payment_method_id` that is returned by the `Stripe` client SDK. Passing this to us will confirm that the payment method was successfully added through Stripe and sync it with the customer's Account on Light. The payment method will not be immediately charged through these APIs; rather, it is saved to the customer's account for regular auto-pay invoice billing based on their next billing cycle. By default, payment methods are charged for the amount due on an invoice 16 days after the invoice is generated. ### 9. Confirm enrollment [image] After an Account has confirmed their address, accepted a plan, added necessary identity information, supplied SSN for credit check, and added a payment method, the enrollment can be finalized. Finalizing an enrollment is essentially completing a checkout for the enrollment. You can see if all of the steps are completed by checking the `/account` API response enrollment fields. If `enrollment.can_finalize_enrollment` is true, then the enrollment can be finalized. If some of the steps are not completed, then check the other fields in the `enrollment` object to see what is needed. It can take anywhere from 2 to 72 business hours to complete the enrollment process depending on the utility and the time of day the enrollment was initiated, if the start date selected was the same day. You can be informed as soon as an account goes live by using [webhooks](/webhooks). We recommend showing a confirmation screen similar to the one above until the Account's service is active. Once the utility has accepted the enrollment, the `is_service_active` field will be marked as active in the `/account` API response. The utility will also supply their usage history for the previous 12 months if available when enabling the service. This information is not received at the exact same time and may be available some hours before or after the service is active. ## Related webhooks Webhooks allow your application to receive real-time notifications about events that occur within the Light platform. This enables you to build responsive and up-to-date integrations without the need for constant polling. [Learn more](/webhooks) ### `enrollment.plan_requested` Triggered when a customer requests a plan. This event won't have a location set, but will have an account since the location isn't saved until the plan is accepted. ### `enrollment.plan_accepted` Triggered when a customer accepts an electricity plan. ### `enrollment.identity_updated` Triggered when a customer's identity information is updated. ### `enrollment.finalized` Triggered when an enrollment is finalized. ### `account.payment_method_added` Triggered when a new payment method is added to an account. ### `location.service_active` Triggered when electricity service becomes active for a location. ### `location.usage_history_available` Triggered when usage history becomes available for a location. --- # billing > https://docs.light.dev/guides/billing # Billing ## Intro The retail-choice electricity market requires a certain method to bill customers that is based on metered usage. This section covers how to retrieve and update payment methods, billing addresses, and access invoice information. Only `AccountToken` [authentication](/authentication) is needed to access billing information. The billing experience can be completely integrated into your existing billing experience by using our API. Light also provides two embedded flow scopes ([prebuilt UI](/prebuilt-ui#embedded-flows)) that you can embed in your UI if you prefer. We recommend using the API experience to create a bespoke billing experience within your existing customer dashboard. The embedded flow is great for shipping a complete billing management experience fast within an existing product. ## Embedded flow [image] The embedded flow lets you skip all of the UI work needed to create a billing management flow. Use the `billing` and `update-payment-method` scopes to surface these specific experiences. [Learn more](/prebuilt-ui#tutorial) - The `billing` scope includes a list of invoices and their details. It also provides a way to manually pre-pay an invoice. - The `update-payment-method` scope lets you update/change a payment method for an account. ## API The API requires you retrieve a user's `AccountToken` for the billing experience. Refer to the [authentication docs](/authentication) for more details. ### View payment method [image] To see what payment method is currently associated with the account, use the [`GET /account/billing/payment-method`](/api-reference#tag/account/GET/v1/account/billing/payment-method) API. This will return the payment method, including its last four digits, expiration date, and type. ### Update payment method Updating a payment method is the same API endpoints as adding a new payment method. View those instructions in the [enrollment guide](/guides/enrollment#8-add-a-payment-method). ### View invoices [image] To retrieve a list of invoices for the customer, use the [`GET /account/billing/invoices`](/api-reference#tag/account/GET/v1/account/billing/invoices) API. This will return a paginated list of invoices with details such as the invoice date, due date, amount, and status. It is recommended to surface if an invoice is paid, voided, or past due. It can also be helpful to surface the payment due date when the auto-pay will attempt to pay the invoice. For the most simple implementation, you can list these invoices with links to their PDFs. If you want a more branded experience, we recommend using the "View invoice details" API below once an invoice is selected. ### View invoice details [image] To get detailed information about a specific invoice, use the [`GET /account/billing/invoices/{invoice_id}`](/api-reference#tag/account/GET/v1/account/billing/invoices/{invoice_id}) API. This will return comprehensive details including line items, usage data, charges, and payment information. Map over `charges_groups` and their charges to display a detailed breakdown without needing to open the PDF. ### View billing address [image] You can retrieve the current billing address using the [`GET /account/billing/address`](/api-reference#tag/account/GET/v1/account/billing/address) API. This will return the current billing address information associated with the customer's account. The address fields will be populated regardless of whether the `same_as_service_address` field is set to `true` or `false`. If `same_as_service_address` is true, Light populates the address fields with the service address information. ### Update billing address [image] Most of the time the billing address will be the same as the service address. In this case, you don't need to set the billing address and the service address will be used. Use the [`PUT /account/billing/address`](/api-reference#tag/account/PATCH/v1/account/billing/address) API to update the billing address. ## Related webhooks Webhooks allow your application to receive real-time notifications about events that occur within the Light platform. This enables you to build responsive and up-to-date integrations without the need for constant polling. [Learn more](/webhooks) ### `account.payment_method_added` Triggered when a new payment method is added to an account. ### `account.billing_address_updated` Triggered when a billing address is updated. ### `account.payment_failed` Triggered when a payment for an invoice fails for an account. ### `account.payment_successful` Triggered when a payment for an invoice is successful for an account. --- # Quickstart > https://docs.light.dev/quickstart # Get started with the quickstart In the quickstart, we'll show you how to: - Set up a sandbox app and get app credentials in the [Light dashboard](https://dashboard.light.dev/) - Use the API and an [embedded flow](/prebuilt-ui#embedded-flows) (prebuilt UI) to let customers enroll in your power plan - Verify customers are able to enroll ### Prerequisites - An account or invite to the [Light dashboard](https://dashboard.light.dev/) - An existing web or native mobile app to add the enrollment experience to - If not, use [our example app](https://github.com/light-technology/example-app) ## 1. Set up a sandbox app and get app credentials in the Light dashboard 1. Log in to the [Light dashboard](https://dashboard.light.dev/) 2. On the Apps page, create a new app that uses the sandbox environment 3. Select the app you just created and go to API Keys from the sidebar 4. Create a new API key and store the `AppToken` somewhere for later We now have an `AppToken` that can be used on all requests for authentication. An additional customer `AccountToken` is needed to complete actions on behalf of the customer, but that's not needed in this quickstart. Make sure you keep your `AppToken` a secret and only use it on the server side of your project to hide it from users. See how we use Next.js serverside app requests to protect our `AppToken` in our [our example app](https://github.com/light-technology/example-app/tree/main/app/api). If you want to make requets directly from your client, look into the `AccountToken`. [Learn more](/authentication) ## 2. Create user's Light Account Before showing the embedded enrollment flow, we need a customer's Light account. Create an account by sending a `POST` to [`/v1/app/accounts`](/api-reference#tag/app-accounts/POST/v1/app/accounts): In a real situation, you should save the customer's `account_uuid` to your own database and avoid creating duplicate accounts on Light. ## 3. Launch embedded flow Then, we need to get a URL that includes the enrollment flow's prebuilt UI. We can use the `uuid` in the last request's response to get a URL specific to your current customer. We place that `uuid` as the `account_uuid` in a POST to [`/v1/app/accounts/{account_uuid}/flow-login`](/api-reference#tag/app-accounts/POST/v1/app/accounts/{account_uuid}/flow-login): ## 4. Surface enrollment flow at the right moment The enrollment flow is usually displayed after a user clicks a button or navigates to another page within an iframe or webview. Display the flow in an iframe or webview: ```tsx interface FlowIframeProps { url: string; } const FlowIframe: React.FC = ({ url }) => { return (
``` #### 3. Close the enrollment flow We need to close the embedded flow once a user chooses to exit the flow. Close the embedded flow by listening for the `light-flow-close` event emitted by the embedded flow. ```javascript window.addEventListener("message", function (event) { const eventType = event.data?.type; if (!eventType) { return; } if (eventType === "light-flow-close") { // TODO: remove the Light iframe or webview } }); ``` ## No-code web app On a deadline? We also have a prebuilt app that you can white-label with no-code to get started. Reach out for us to get started. ## No-code flows These are our same prebuilt UI flows that you can embed, except hosted by Light. These can be used for out-of-band experiences like renewals to save you time of building for all paths at launch. They are typically launched via a pre-authenticated email link, providing a quick and branded interaction for the customer. ### Scopes The following scopes are currently supported, but more scopes will be added in the future. Reach out to us if you have a specific use case that you would like to see supported. Scopes: - `renewal` - Launches the no-code flow with the renewal experience. --- # usage > https://docs.light.dev/guides/usage # Energy usage ## Intro Helping track customer's energy usage can help them understand their bills and project future usage. Only `AccountToken` [authentication](/authentication) is needed to access usage information. ## API Our usage API endpoints differ based on the time interval you want to receive usage data. The API requires `AccountToken` (refer to the [authentication docs](/authentication) for more details). Depending on the customer's utility, we may be able to access up to two years of historical data for a single customer. The usage data often lags by a day or two. [image] The usage data returned by these APIs are estimated for analyzing usage and information, but may not align exactly to the meter reading. This is due to multiple factors including that the meter read happens and different times of the day than the interval boundaries. All of these APIs provide both import and export usage data. Import refers to energy consumed from the grid, while export refers to energy sent back to the grid (in the case of solar panels or battery storage). ### Get monthly usage This endpoint returns an entire year of usage separated by month. In addition, it provides links to the previous and next year of usage if applicable. For this interval, use the [`GET /v1/account/locations/{location_uuid}/usage/monthly`](/api-reference#tag/locations/GET/v1/account/locations/{location_uuid}/usage/monthly) API. ### Get daily usage This endpoint returns an entire month of usage separated by day. In addition, it provides links to the previous and next month of usage if applicable. For this interval, use the [`GET /v1/account/locations/{location_uuid}/usage/daily`](/api-reference#tag/locations/GET/v1/account/locations/{location_uuid}/usage/daily) API. ### Get 15-minute interval usage This endpoint returns an entire month of usage separated by 15-minute intervals. In addition, it provides links to the previous and next month of usage if applicable. For this interval, use the [`GET /v1/account/locations/{location_uuid}/usage/intervals`](/api-reference#tag/locations/GET/v1/account/locations/{location_uuid}/usage/intervals) API. Most days have 96 intervals (four 15-minute intervals per hour for a 24 hour day). However, for locations participating in daylight savings time, this could be 92 intervals (spring forward) or 100 intervals (fall back) on some days. We receive this data one day at a time, so there will never be a partial day represented. --- # renewal > https://docs.light.dev/guides/renewal # Renewal ## Intro Once a customer is approaching the end of their contract, they will be sent reminder emails to accept a renewal contract. They will be switched to a month-to-month plan if a renewal plan is not accepted by the original contract expiration date. By default, you can rely on the no-code flow for renewals to handle the experience and regulatory needs. We send renewal emails once a customer is eligible for renewal (90 days out from expiration), with links to hosted no-code flows. These flows provide a branded experience where customers can see a renewal rate and accept it to start once their current term ends. The default renewal terms use the same pricing model as is used for new customers. You can work with our team to adjust default overrides like extending term length or changing pricing on renewal if desired. Most partners start with our default renewal behavior at launch. Once you have a good handle on your new customer flows, you may consider building a custom renewal flow if you need specific control over the renewal experience or want to integrate it directly into your app. ## Prebuilt UI ### No-code flow The default renewal experience uses our [no-code flow](/prebuilt-ui#no-code-flows) hosted by Light. When a customer is eligible for renewal, they receive an email with a link to a branded flow where they can: 1. Review their renewal rate and plan details 2. See when their current contract ends 3. Accept the renewal to start service on the next day after expiration This happens automatically without any implementation required from your side. ### Custom embedded flow If you want to host the renewal flow in your own app, we offer a prebuilt UI embedded flow using the same renewal interface. This lets you keep customers within your domain and app experience but without having to build the entire flow from scratch. To use this option: 1. Implement the prebuilt UI with the `renewal` scope in your app 2. Let us know where you've set up the flow 3. We'll update our renewal emails to link to your app instead of the default hosted flow [Learn more about prebuilt UI](/prebuilt-ui#launching-a-prebuilt-ui-flow) ## API If you want to customize your renewal process beyond the prebuilt flows, you can use our API directly. The prebuilt flows are built on top of our public API, so you can achieve the same behavior with a native implementation. The API requires you retrieve a user's `AccountToken` for the renewal experience. Refer to the [authentication docs](/authentication) for more details. ### Request renewal plans To show customers their available renewal plans, use the [`POST /v1/account/locations/{location_uuid}/renewal-plans/request`](/api-reference#tag/Renewal-Plans/POST/v1/account/locations/%7Blocation_uuid%7D/renewal-plans/request) API. This returns the renewal plans available for an active service location. The response includes plan details like rates, term length, and start dates. For typical renewals, the `earliest_start_date` and `latest_start_date` will be the same date (the day after the current contract ends). ### Accept renewal plan Once a customer selects a renewal plan, use the [`POST /v1/account/locations/{location_uuid}/renewal-plans/accept`](/api-reference#tag/Renewal-Plans/POST/v1/account/locations/%7Blocation_uuid%7D/renewal-plans/accept) API to finalize their selection. You'll need to pass: - `plan_uuid`: The unique identifier for the selected renewal plan - `service_start_date`: The start date (typically the `earliest_start_date` from the request) - `terms_accepted`: Must be `true` to confirm the customer accepted the terms ### Customizing renewal rates If you want to offer different rates or plans to customers on renewal, you can use the general enrollment plans API instead. Call [`POST /v1/app/accounts/enroll/plans/request`](/api-reference#tag/app-enrollment/POST/v1/app/accounts/enroll/plans/request) to request a custom plan, then accept the renewal using the renewal accept API with the `service_start_date` set to the day after their current contract ends. This approach gives you full control over what plans and rates you offer at renewal time. ## Related webhooks Webhooks allow your application to receive real-time notifications about events that occur within the Light platform. This enables you to build responsive and up-to-date integrations without the need for constant polling. [Learn more](/webhooks) ### `location.renewal_plan_requested` Triggered when a customer requests renewal plans for their location. ### `location.renewal_plan_accepted` Triggered when a customer accepts a renewal plan for their location. --- # webhooks > https://docs.light.dev/webhooks # Webhooks Webhooks allow your application to receive real-time notifications about events that occur within the Light platform. This enables you to build responsive and up-to-date integrations without the need for constant polling. It may not be necessary to use webhooks depending on how you plan to use the Light API. However, they can be useful when you use the Account API or Pre-built UI to talk directly from your client application to the Light API, to notify your backend of key events. ## Setting up webhooks You can set up webhooks for your app from the Developer settings in your dashboard. When setting up a webhook, you'll provide us with an API endpoint URL where we'll send webhook events. You'll also receive a secret key that you can use to verify the authenticity of webhook events as every webhook event is signed with an HMAC signature. ## Supported webhooks Currently, we support the following webhook events: - `enrollment.plan_requested`: Triggered when a customer requests a plan - This event won't have a location set, but will have an account since the location isn't saved until the plan is accepted. - `enrollment.plan_accepted`: Triggered when a customer accepts an electricity plan - `enrollment.identity_updated`: Triggered when a customer's identity information is updated - `enrollment.finalized`: Triggered when an enrollment is finalized - `account.payment_method_added`: Triggered when a new payment method is added to an account - `account.billing_address_updated`: Triggered when a billing address is updated - `account.payment_failed`: Triggered when a payment for an invoice fails for an account - `account.payment_successful`: Triggered when a payment for an invoice is successful for an account - `location.service_active`: Triggered when electricity service becomes active for a location - `location.service_canceled`: Triggered when electricity service is canceled for a location - `location.usage_history_available`: Triggered when usage history becomes available for a location - `location.renewal_plan_requested`: Triggered when a customer requests renewal plans for their location - `location.renewal_plan_accepted`: Triggered when a customer accepts a renewal plan for their location - `comparison_invoice.processed`: Triggered when a comparison invoice is done being processed (either successfully or unsuccessfully) ## Webhook payload format Webhook payloads are sent as JSON in the body of a POST request. The root keys of all webhooks will look the same, but the data object will vary depending on the event type. Here's an example of a webhook payload: ```javascript { "uuid": "123e4567-e89b-12d3-a456-426614174000", "created_at": "2023-04-01T12:00:00Z", "api_version": "v1", "event": "enrollment.plan_accepted", "data": { "account": { "uuid": "98765432-e89b-12d3-a456-426614174000", "first_name": "John", "last_name": "Doe", "email": "john.doe@example.com" // ... other account details }, "location": { "uuid": "12365432-e89b-1a23-a356-323634134300", "service_start_date": "2025-05-03", "final_service_date": null, // ... other location details } } } ``` The "enrollment._" and "account._" events will contain an `account` object mirroring that of the [Account API](/api-reference#tag/account/GET/v1/account) response. The "location._" events will always contain a `location` object and an `account` object. The "account._" events will always include an `account` objects, and sometimes a `location` if one is set for the `account`. ## Webhook delivery We attempt to deliver webhooks up to 3 times total with a backoff if we don't receive a 2xx response from your server. All attempts will be made within a 10-minute window. If all attempts fail, the webhook will be discarded. Webhook attempts will timeout after 10 seconds. So if you need to do significant processing or network calls after receiving a webhook we would recommend to return a 200 and spawn an asynchronous job on your system for the extended processing. ## Best practices 1. We recommend using webhooks as triggers to fetch data that would be eventually fetched either way, and not as a source of truth. Webhooks can be missed or delivered out of order due to a variety of issues from the sending or receiving side. 2. Design your system to handle occasional missing webhooks or duplicate deliveries, even though these scenarios are rare. 3. Use webhook signatures for securing your webhook endpoint. 4. The webhook request will timeout after 10 seconds, so ensure your webhook endpoint can respond within that time frame. We recommend processing the webhook payload asynchronously to avoid blocking the response. 5. For webhooks types that you don't care about, respond with a 200 status code to acknowledge receipt of the webhook. Responding with a 2xx status code will prevent the webhook from being retried. If we receive an abundance of 4xx or 5xx responses from an endpoint, we may stop sending webhooks to your endpoint in the future. ## Webhook signatures To ensure the security and authenticity of webhook payloads, we sign each webhook using HMAC with SHA-256. The signature is included in the `Light-Signature-v1` header of the webhook request. The header value is in the format: `{timestamp}.{hmac}` Where: - `timestamp` is the Unix timestamp to the nearest second when the webhook was sent - `hmac` is the hexadecimal representation of the HMAC-SHA256 digest To verify the signature, you should: 1. Split the `Light-Signature-v1` header value into its timestamp and hmac components. 2. Verify that the timestamp is not too old (e.g., older than 1 hour) to limit replay attack exposure. 3. Generate your own HMAC-SHA256 digest using the timestamp and the raw payload of the webhook separated by a period. 4. Compare the resulting digest with the hmac provided in the header using a secure, constant-time comparison method. Here's an example implementation of signature verification in Python: ```python import hashlib import hmac import time def compare_signature( signing_secret: str, payload: str, provided_signature: str ) -> bool: """ Compare the provided Light-Signature-v1 signature to the computed expected signature using a sha256 HMAC digest. """ split = provided_signature.split(".") if len(split) != 2: return False timestamp, hmac_digest = split # if the timestamp is older than 1 hour, reject the request to avoid # replay attacks if int(timestamp) < time.time() - 3600: return False message_bytes = f"{timestamp}.{payload}".encode("utf-8") expected_digest = hmac.new( signing_secret.encode("utf-8"), message_bytes, hashlib.sha256, ).hexdigest() # use a constant-time comparison to avoid timing attacks return hmac.compare_digest(hmac_digest, expected_digest) ``` By implementing signature verification, you can ensure that the webhooks you receive are genuine and have not been tampered with. ---