Embed Texture Connect
Add Texture Connect to your web or mobile application with an SDK or the API
Texture Connect is the OAuth widget your customers use to authorize access to their energy devices. This page covers embedding it in your own application. To send a customer a link without writing code, see Create a link in the dashboard.
Choose Your Integration Method#
| Method | Best for |
|---|---|
| Texture API | Custom implementations with full control over the flow |
| Headless Connect | Enterprise: fully white-labeled experience, your own UI, Texture returns OAuth URL directly |
| JavaScript/TypeScript SDK | Web applications — handles the popup window and OAuth flow for you |
| React SDK | React applications — useCreateConnectSession hook |
| React Native SDK | iOS/Android apps — TextureConnect component |
Headless Connect#
Headless Connect must be enabled for your organization before use. Contact your Texture Solutions team to request access.
Headless Connect gives you complete control over every screen the user sees. Instead of opening the Texture-hosted Connect UI, your application collects all necessary information, creates the connect session, and redirects the user directly to the manufacturer's OAuth page — all within your own branded experience. Texture handles the OAuth callback and forwards the result to your redirectUrl.
Prerequisites#
Before initiating a headless session:
-
A manufacturer that supports OAuth for a fully headless flow. Manufacturers that use serial number capture instead of OAuth, such as ChargePoint, EG4, Emporia, FranklinWH, and Generac, are accepted, but the customer finishes on the Texture-hosted page. See Serial number manufacturers.
-
Exactly one manufacturer in
manufacturerFilters.manufacturers -
A
redirectUrlyour application controls — Texture redirects here after OAuth completes -
One of the following three request combinations for customer and site identification:
Scenario Required fields How site is resolved Existing customer with site customerIdCustomer's first associated site. Use customerId+siteIdto control which site.Existing customer, explicit site customerId+siteIdExplicit siteIdNew customer Full customerInfo(firstName, lastName, email, location)New site created from customerInfo.locationAny other combination —
siteIdwithoutcustomerId, partialcustomerInfo— is rejected with a validation error.
Creating a Headless Session#
Set mode: "headless" in your POST /v1/connections request:
curl -X POST https://api.texturehq.com/v1/connections \
-H "Content-Type: application/json" \
-H "Texture-Api-Key: your_api_key" \
-d '{
"mode": "headless",
"manufacturerFilters": { "manufacturers": ["tesla"] },
"customerId": "cllh2buhk000008la0ln6cb80",
"siteId": "cllh2buhk000008la0ln6cb81",
"redirectUrl": "https://yourapp.com/oauth/callback"
}'Or create a new customer and site on the fly:
curl -X POST https://api.texturehq.com/v1/connections \
-H "Content-Type: application/json" \
-H "Texture-Api-Key: your_api_key" \
-d '{
"mode": "headless",
"manufacturerFilters": { "manufacturers": ["tesla"] },
"customerInfo": {
"firstName": "Jane",
"lastName": "Smith",
"email": "jane@example.com",
"location": {
"streetOne": "123 Main St",
"city": "Austin",
"state": "TX",
"postalCode": "78701",
"country": "US"
}
},
"redirectUrl": "https://yourapp.com/oauth/callback"
}'Response#
{
"customerId": "cllh2buhk000008la0ln6cb80",
"linkUrl": "https://connect.texturehq.com/link_...",
"linkToken": "link_e47b6d39dd3e6b3ab55969f55dc88ae6",
"linkSessionId": "cllh2buhk000008la0ln6cb82",
"expiresAt": "2026-04-20T12:30:00Z",
"siteId": "cllh2buhk000008la0ln6cb81",
"authorization": {
"type": "oauth_redirect",
"url": "https://auth.tesla.com/oauth2/v3/authorize?client_id=..."
}
}| Field | Description |
|---|---|
authorization.type | oauth_redirect for OAuth manufacturers, or user_input for manufacturers that use serial number capture. |
authorization.url | The manufacturer's OAuth authorization URL. Redirect the user here immediately — sessions expire, so don't delay. Present only when authorization.type is oauth_redirect. |
expiresAt | When this session expires (ISO 8601). Redirect the user to authorization.url before this time or the session will be invalid. |
customerId | Texture customer ID — existing or newly created. |
siteId | The site associated with this session. |
linkSessionId | Session identifier — useful for troubleshooting with Texture support. |
linkUrl | URL to the Texture-hosted Connect UI. For OAuth manufacturers, use authorization.url instead. For user_input, send the customer here. |
linkToken | Internal token that identifies this session. Not needed for headless flows. |
Serial number manufacturers#
For manufacturers that use serial number capture instead of OAuth, the response has authorization.type set to user_input and no authorization.url. Send the customer to linkUrl, where they enter their device's serial number on the Texture-hosted page. When they continue from the success screen, Texture sends them to your redirectUrl with texture_scoped_key and texture_customer_id.
Handling the OAuth Callback#
After OAuth completes, Texture redirects to your redirectUrl with these query parameters:
On success:
| Parameter | Description |
|---|---|
texture_scoped_key | Bearer token for this connection — store securely, use it for subsequent device API calls. |
texture_customer_id | Texture customer ID. |
texture_existing_devices | true if this customer already had connected devices. Omitted otherwise. |
On failure:
Texture redirects with a texture_error query parameter set to one of these values:
texture_error value | Description |
|---|---|
connect_session_expired | Session timed out before OAuth completed. |
connect_session_already_completed | OAuth was already completed for this session (replay protection). |
oauth_state_mismatch | CSRF state validation failed. |
connection_failed | Token exchange or device connection creation failed. May be transient — retry may succeed. |
device_incompatible | User's device doesn't support the requested permissions or has no compatible devices. |
subscription_required | Device account lacks an active connected-services subscription with the manufacturer. |
unknown_error | Unrecognized error. A companion texture_error_raw parameter contains the original manufacturer error string for debugging. |
access_denied | User declined authorization. |
server_error | Manufacturer authorization server error. Retry may succeed. |
temporarily_unavailable | Manufacturer server temporarily unavailable. Retry after a delay. |
const params = new URLSearchParams(window.location.search);
const error = params.get("texture_error");
if (error) {
const errorRaw = params.get("texture_error_raw"); // only present when error === "unknown_error"
console.error("OAuth failed:", error, errorRaw);
} else {
const scopedKey = params.get("texture_scoped_key");
const customerId = params.get("texture_customer_id");
// Store scopedKey securely and use it for subsequent API calls
}Configuration Reference#
Session Options#
| Field | Required | Description |
|---|---|---|
redirectUrl | Yes | URL where users land after completing the flow. For web: https://mycompany.com/app. For mobile: a deep link like myapp://device/connected. Texture appends texture_scoped_key as a query parameter. |
referenceId | No | Your identifier for this customer — a userId, site ID, or anything you use to look them up later. If not supplied, Texture generates one of the form texture-generated:<uuidv7>. |
tags | No | Array of strings for grouping devices (e.g. ["boston", "program-A"]). Tags are editable later via API. |
clientName | No | Your company or application name as it appears in the Connect UI. Defaults to your organization name. |
customerId | No | An existing Texture customer ID. Takes priority over customerInfo and referenceId. If the customer has multiple sites, Texture uses their first associated site — pair with siteId to specify which one. |
siteId | No | ID of an existing site to associate with this session. In standard mode, takes highest priority over site resolved from customerId or customerInfo.location. In headless mode, must be paired with customerId. |
mode | No | standard (default) or headless. Headless returns an OAuth URL instead of showing the Texture-hosted UI. |
manufacturerFilters#
Restrict which manufacturers appear in the Connect flow.
| Property | Description | Example |
|---|---|---|
manufacturers | Limit to specific manufacturers. If exactly 1 is specified, the manufacturer selection screen is skipped. | ["enphase", "tesla"] |
deviceTypes | Limit to manufacturers that have devices of these types. | ["battery"] |
customerInfo#
Pre-populate customer details. If firstName, lastName, email, and location are all supplied, the customer info collection form is skipped entirely.
| Property | Description | Example |
|---|---|---|
firstName | Customer first name | "John" |
lastName | Customer last name | "Doe" |
email | Customer email | "example@example.com" |
phone | Customer phone | "555-555-0100" |
location | Customer site address — all fields required if provided | {"streetOne": "1600 Pennsylvania Avenue NW", "city": "Washington", "state": "DC", "postalCode": "20500", "country": "US"} |
Quick Start: SDK#
Installation#
JavaScript/TypeScript#
npm install @texturehq/connect-sdk
or with Yarn:
yarn add @texturehq/connect-sdk
You can also load via CDN. Pin an exact version so a future release can't change the flow under you:
<script src="https://unpkg.com/@texturehq/connect-sdk@0.8.1/dist/connect-sdk.iife.js"></script>React#
npm install @texturehq/react-connect-sdk
or with Yarn:
yarn add @texturehq/react-connect-sdk
React Native#
npm install @texturehq/react-native-connect-sdk
or with Yarn:
yarn add @texturehq/react-native-connect-sdk
Usage#
JavaScript/TypeScript usage#
import { createConnectSession } from "@texturehq/connect-sdk";
const texture = createConnectSession({
connectApiKey: "<connect-api-key>",
connectOptions: {
referenceId: "<reference-id-from-your-system>",
clientName: "Name for your application",
redirectUrl: "https://your-application.com/redirect",
tags: ["tag1", "tag2"],
manufacturerFilters: { manufacturers: ['honeywell', 'daikin'] },
customerInfo: {
email: "example@example.com",
phone: "555-555-0100",
firstName: "John",
lastName: "Doe",
},
},
onSuccess: ({ scopedKey }) => {
// Send scopedKey to your backend and store it — do not log it.
// It is a bearer token for this customer's devices. See /api/scoped-keys.
},
onError: ({ type, reason }) => {
// type 'popup-blocked' — popup window was blocked
// type 'popup-closed' — user closed popup before completing the flow
},
});
// Open the popup
texture.open();
// Optional: control popup dimensions
texture.open({ width: 500, height: 600 });
// Close programmatically
texture.close();
// Get the connect URL
const url = texture.url;What to do with the scopedKey: send it to your backend and store it against the customer record. The scoped key is a bearer token that lets you call the Texture API for that specific customer's devices — it does not grant access to other customers' data. See Scoped Keys for usage details.
React usage#
import { useCreateConnectSession } from "@texturehq/react-connect-sdk";
function App() {
const { open } = useCreateConnectSession({
connectApiKey: "<connect-api-key>",
connectOptions: {
referenceId: "<reference-id-from-your-system>",
clientName: "Name for your application",
redirectUrl: "https://your-application.com/redirect",
tags: ["tag1", "tag2"],
manufacturerFilters: { manufacturers: ['honeywell', 'daikin'] },
customerInfo: {
email: "example@example.com",
phone: "555-555-0100",
firstName: "John",
lastName: "Doe",
},
},
onSuccess: ({ scopedKey }) => {
// Send scopedKey to your backend and store it against the customer record.
// It is a bearer token scoped to this customer's devices — see /api/scoped-keys.
},
onError: ({ type, reason }) => {
// type 'popup-blocked' — popup window was blocked
// type 'popup-closed' — user closed popup before completing the flow
},
});
return (
<button onClick={() => open()}>Connect your device</button>
);
}React Native usage#
Set redirectUrl to your app's custom URL scheme (e.g. myapp://).
The React Native SDK uses positional arguments for callbacks, not objects. onSuccess receives (scopedKey: string) and onError receives (type: string, reason: string) — not the destructured form used in the JS/React SDKs.
import { TextureConnect } from '@texturehq/react-native-connect-sdk';
import { Text } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
export default function ConnectScreen() {
return (
<GestureHandlerRootView style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<TextureConnect
connectApiKey="<YOUR_CONNECT_API_KEY>"
connectOptions={{
clientName: 'Name for your application',
referenceId: '123',
redirectUrl: 'example://',
manufacturerFilters: { manufacturers: ['honeywell', 'daikin'] },
customerInfo: {
email: "example@example.com",
phone: "555-555-0100",
firstName: "John",
lastName: "Doe",
},
}}
onError={(type, reason) => { /* handle error */ }}
onSuccess={(scopedKey) => { /* send scopedKey to your backend — do not log */ }}
>
<Text>Tap here to connect a device.</Text>
</TextureConnect>
</GestureHandlerRootView>
);
}Open Source Examples#
See working examples in our examples repository.