Master HTTP requests, API integration, JSON parsing, email notifications, and dataserver functions to connect your virtual world with external services.
Take your LSL scripting to the next level by learning how to communicate beyond the boundaries of your virtual world. This advanced course teaches you to fetch data from external APIs, send emails, parse JSON responses, and utilize the powerful dataserver for notecard reading and inventory management.
HTTP (Hypertext Transfer Protocol) is the foundation of data communication on the web. In LSL, llHTTPRequest() allows your scripts to communicate with external web services, fetch real-time data, interact with APIs, and integrate your virtual world with the broader internet.
Syntax: key llHTTPRequest(string url, list parameters, string body)
| Parameter | Description |
|---|---|
url |
The web address to send the request to (must be HTTPS) |
parameters |
List of options like HTTP method, custom headers, timeout |
body |
Data to send with POST/PUT requests (empty string for GET) |
| Returns | A unique key to identify this request in the response |
Let's create a script that fetches a random cat fact from a public API. This demonstrates the complete workflow: sending a request, handling the response, and displaying results.
// Simple HTTP GET Request Example
// Fetches a random cat fact from an API
// Global variable to store the API URL
string g_api_url = "https://catfact.ninja/fact";
// We'll store the request ID to make sure the response we get is the one we're expecting.
// We'll store the request ID to make sure the response we get is the one we're expecting.
key g_request_id;
default
{
state_entry()
{
llSetText("Click me for a cat fact!", <1,1,1>, 1.0);
}
touch_start(integer total_number)
{
llSetText("Fetching fact...", <1,1,0>, 1.0);
// Define the parameters for our request. We are doing a "GET" request.
list http_params = [HTTP_METHOD, "GET"];
// Send the request!
// The body is empty because we are only getting data, not sending it.
// We store the returned key in our global variable.
g_request_id = llHTTPRequest(g_api_url, http_params, "");
}
// This event fires when the web server responds.
http_response(key request_id, integer status, list metadata, string body)
{
// First, check if this response matches our request.
// This is important if your script makes multiple different requests.
if (request_id == g_request_id)
{
// Next, check if the request was successful. Status 200 means OK.
if (status == 200)
{
// Success! The 'body' variable now contains the data.
// For this API, it's a JSON string like:
// {"fact":"A cat's brain is more similar to a human's brain than a dog's.","length":78}
llOwnerSay("â
API Response received!");
llOwnerSay("Raw JSON: " + body);
// Display success
llSetText("â Fact received! Check chat.", <0,1,0>, 1.0);
}
else
{
// The request failed! Let's report the error.
llSetText("Error: " + (string)status, <1,0,0>, 1.0);
llOwnerSay("â HTTP request failed with status: " + (string)status);
}
}
}
}
http_response event fires when the server repliesJSON (JavaScript Object Notation) is a lightweight data format used by most modern APIs. It structures data as key-value pairs, making it easy to read and parse.
Example JSON response:
{
"fact": "Cats can rotate their ears 180 degrees.",
"length": 42,
"verified": true
}
Syntax: string llJsonGetValue(string json, list specifiers)
json: The raw JSON string from the API responsespecifiers: A list acting as a "path" to the value you want (e.g., ["fact"])// HTTP Request with JSON Parsing
// Extracts specific data from API response
string g_api_url = "https://catfact.ninja/fact";
key g_request_id;
default
{
state_entry()
{
llSetText("Touch for cat fact", <1,1,1>, 1.0);
llOwnerSay("Cat Fact Fetcher Ready!");
}
touch_start(integer total_number)
{
llSetText("Fetching...", <1,1,0>, 1.0);
list http_params = [HTTP_METHOD, "GET"];
g_request_id = llHTTPRequest(g_api_url, http_params, "");
}
http_response(key request_id, integer status, list metadata, string body)
{
if (request_id == g_request_id)
{
if (status == 200)
{
// Parse the JSON to extract just the "fact" field
string fact = llJsonGetValue(body, ["fact"]);
// Check if parsing was successful
if (fact != JSON_INVALID)
{
// Display the extracted fact
llSay(0, "đą Cat Fact: " + fact);
llSetText("Touch for another fact", <0,1,0>, 1.0);
// Optional: Extract the length field
string lengthStr = llJsonGetValue(body, ["length"]);
integer length = (integer)lengthStr;
llOwnerSay("Fact length: " + (string)length + " characters");
}
else
{
llOwnerSay("â Failed to parse JSON data");
llSetText("Parse error", <1,0,0>, 1.0);
}
}
else
{
llOwnerSay("â HTTP Error: " + (string)status);
llSetText("Request failed", <1,0,0>, 1.0);
}
}
}
}
For complex JSON with nested objects, use multiple specifiers:
{
"user": {
"name": "JohnDoe",
"stats": {
"level": 42
}
}
}
// To get the level value:
string level = llJsonGetValue(json, ["user", "stats", "level"]);
// Returns: "42"
JSON_INVALID before using it. This prevents errors from malformed or unexpected API responses.
The llEmail() function allows scripts to send email messages to real-world email addresses. This is perfect for alerts, notifications, logs, and connecting in-world events to external systems.
Syntax: llEmail(string address, string subject, string message)
| Parameter | Description |
|---|---|
address |
The recipient's email address |
subject |
Email subject line (max 255 characters) |
message |
Email body content (max 4096 characters) |
// Security Alert Email Notifier
// Sends email when unauthorized person enters area
string g_owner_email = "your-email@example.com";
key g_owner_key;
default
{
state_entry()
{
g_owner_key = llGetOwner();
llSensorRepeat("", "", AGENT, 10.0, PI, 5.0); // Scan every 5 seconds
llOwnerSay("Security system active - Email alerts enabled");
}
sensor(integer num_detected)
{
integer i;
for (i = 0; i < num_detected; i++)
{
key detected_key = llDetectedKey(i);
// If it's not the owner, send alert
if (detected_key != g_owner_key)
{
string intruder_name = llDetectedName(i);
string location = llGetRegionName() + " " + (string)llGetPos();
string subject = "â ī¸ Security Alert - Unauthorized Entry";
string message = "SECURITY BREACH DETECTED!\n\n"
+ "Intruder: " + intruder_name + "\n"
+ "Location: " + location + "\n"
+ "Time: " + llGetTimestamp() + "\n"
+ "Key: " + (string)detected_key;
llEmail(g_owner_email, subject, message);
llOwnerSay("â ī¸ Email alert sent about: " + intruder_name);
// Prevent spam - wait before next email
llSleep(60.0);
}
}
}
no_sensor()
{
// Area is clear
}
}
// Weather Alert System
// Fetches weather data and emails severe weather warnings
string g_weather_api = "https://api.weatherapi.com/v1/current.json?key=YOUR_KEY&q=NewYork";
string g_alert_email = "your-email@example.com";
key g_request_id;
default
{
state_entry()
{
llSetText("Weather Monitor Active", <0,1,1>, 1.0);
// Check weather every hour
llSetTimerEvent(3600.0);
llOwnerSay("Weather alert system initialized");
}
timer()
{
// Fetch current weather
list params = [HTTP_METHOD, "GET"];
g_request_id = llHTTPRequest(g_weather_api, params, "");
}
http_response(key request_id, integer status, list metadata, string body)
{
if (request_id == g_request_id && status == 200)
{
// Parse weather condition
string condition = llJsonGetValue(body, ["current", "condition", "text"]);
string temp = llJsonGetValue(body, ["current", "temp_c"]);
// Check for severe conditions
if (llSubStringIndex(llToLower(condition), "storm") != -1 ||
llSubStringIndex(llToLower(condition), "thunder") != -1)
{
string subject = "â ī¸ Severe Weather Alert";
string message = "WEATHER WARNING\n\n"
+ "Condition: " + condition + "\n"
+ "Temperature: " + temp + "°C\n"
+ "Time: " + llGetTimestamp();
llEmail(g_alert_email, subject, message);
llSay(0, "đŠī¸ Severe weather detected! Email sent.");
}
}
}
}
The dataserver is LSL's mechanism for asynchronously reading data from notecards, getting inventory information, and retrieving object/avatar details without blocking script execution.
Notecards are perfect for storing configuration data, dialog text, lists of names, or any text-based information. The llGetNotecardLine() function reads one line at a time.
Syntax: key llGetNotecardLine(string name, integer line)
// Notecard Reader Script
// Reads and displays all lines from a notecard
string g_notecard_name = "config";
key g_notecard_query;
integer g_line_number = 0;
list g_notecard_lines;
default
{
state_entry()
{
llOwnerSay("Reading notecard: " + g_notecard_name);
// Start reading from line 0
g_notecard_query = llGetNotecardLine(g_notecard_name, g_line_number);
}
dataserver(key query_id, string data)
{
// Check this is our query
if (query_id == g_notecard_query)
{
// Check if we've reached the end (EOF = End Of File)
if (data != EOF)
{
// Store this line
g_notecard_lines += [data];
llOwnerSay("Line " + (string)g_line_number + ": " + data);
// Read the next line
g_line_number++;
g_notecard_query = llGetNotecardLine(g_notecard_name, g_line_number);
}
else
{
// Finished reading all lines
llOwnerSay("â
Notecard read complete!");
llOwnerSay("Total lines: " + (string)llGetListLength(g_notecard_lines));
}
}
}
}
// Configuration Reader
// Reads key=value pairs from notecard
string g_config_notecard = "settings";
key g_query;
integer g_line = 0;
// Store configuration values
string g_admin_name;
integer g_max_users;
float g_timer_interval;
default
{
state_entry()
{
llOwnerSay("Loading configuration...");
g_query = llGetNotecardLine(g_config_notecard, g_line);
}
dataserver(key query_id, string data)
{
if (query_id == g_query)
{
if (data != EOF)
{
// Skip empty lines and comments
data = llStringTrim(data, STRING_TRIM);
if (data != "" && llGetSubString(data, 0, 0) != "#")
{
// Parse key=value format
integer equals_pos = llSubStringIndex(data, "=");
if (equals_pos != -1)
{
string key = llStringTrim(llGetSubString(data, 0, equals_pos - 1), STRING_TRIM);
string value = llStringTrim(llGetSubString(data, equals_pos + 1, -1), STRING_TRIM);
// Process configuration
if (key == "admin_name")
g_admin_name = value;
else if (key == "max_users")
g_max_users = (integer)value;
else if (key == "timer_interval")
g_timer_interval = (float)value;
llOwnerSay("Config: " + key + " = " + value);
}
}
// Read next line
g_line++;
g_query = llGetNotecardLine(g_config_notecard, g_line);
}
else
{
llOwnerSay("â
Configuration loaded successfully!");
llOwnerSay("Admin: " + g_admin_name);
llOwnerSay("Max Users: " + (string)g_max_users);
llOwnerSay("Timer: " + (string)g_timer_interval + "s");
}
}
}
}
Example notecard content (save as "settings"):
# Configuration File
# Lines starting with # are comments
admin_name=JohnDoe
max_users=50
timer_interval=30.0
// Inventory Counter
default
{
state_entry()
{
integer texture_count = llGetInventoryNumber(INVENTORY_TEXTURE);
integer sound_count = llGetInventoryNumber(INVENTORY_SOUND);
integer notecard_count = llGetInventoryNumber(INVENTORY_NOTECARD);
integer script_count = llGetInventoryNumber(INVENTORY_SCRIPT);
llOwnerSay("đĻ Inventory Report:");
llOwnerSay("Textures: " + (string)texture_count);
llOwnerSay("Sounds: " + (string)sound_count);
llOwnerSay("Notecards: " + (string)notecard_count);
llOwnerSay("Scripts: " + (string)script_count);
}
touch_start(integer num)
{
// List all notecards
integer i;
integer count = llGetInventoryNumber(INVENTORY_NOTECARD);
llSay(0, "Notecards in inventory:");
for (i = 0; i < count; i++)
{
string name = llGetInventoryName(INVENTORY_NOTECARD, i);
llSay(0, (string)(i+1) + ". " + name);
}
}
}
Production scripts should handle network failures gracefully:
// Robust HTTP Request with Timeout
string g_api_url = "https://api.example.com/data";
key g_request_id;
float g_request_time;
float g_timeout = 30.0; // 30 second timeout
default
{
state_entry()
{
llSetTimerEvent(1.0); // Check every second
}
touch_start(integer num)
{
list params = [
HTTP_METHOD, "GET",
HTTP_CUSTOM_HEADER, "User-Agent", "LSL-Script/1.0"
];
g_request_id = llHTTPRequest(g_api_url, params, "");
g_request_time = llGetTime();
llOwnerSay("Request sent, waiting for response...");
}
timer()
{
// Check if request has timed out
if (g_request_id != NULL_KEY)
{
if ((llGetTime() - g_request_time) > g_timeout)
{
llOwnerSay("â Request timed out after " + (string)g_timeout + " seconds");
g_request_id = NULL_KEY; // Reset
}
}
}
http_response(key request_id, integer status, list metadata, string body)
{
if (request_id == g_request_id)
{
g_request_id = NULL_KEY; // Clear timeout check
if (status == 200)
{
llOwnerSay("â
Success: " + body);
}
else if (status == 404)
{
llOwnerSay("â Error 404: Resource not found");
}
else if (status == 500)
{
llOwnerSay("â Error 500: Server error");
}
else
{
llOwnerSay("â HTTP Error: " + (string)status);
}
}
}
}
// Sending data to an API
string g_api_url = "https://api.example.com/submit";
key g_request_id;
default
{
touch_start(integer num)
{
// Prepare JSON data to send
string json_data = llList2Json(JSON_OBJECT, [
"username", llKey2Name(llDetectedKey(0)),
"action", "login",
"timestamp", llGetTimestamp()
]);
list params = [
HTTP_METHOD, "POST",
HTTP_MIMETYPE, "application/json",
HTTP_CUSTOM_HEADER, "Authorization", "Bearer YOUR_TOKEN_HERE"
];
g_request_id = llHTTPRequest(g_api_url, params, json_data);
llOwnerSay("Sending data: " + json_data);
}
http_response(key request_id, integer status, list metadata, string body)
{
if (request_id == g_request_id)
{
if (status == 200 || status == 201)
{
llOwnerSay("â
Data submitted successfully!");
llOwnerSay("Response: " + body);
}
else
{
llOwnerSay("â Submit failed: " + (string)status);
}
}
}
}
Create a floating text display that shows live Bitcoin prices. Use the CoinGecko API to fetch prices every 5 minutes.
https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usdBuild a visitor registration system that stores names and timestamps in a notecard and emails you a summary daily.
Create an object that changes appearance based on real-world weather using the WeatherAPI service.
Build a multi-page dialog menu system where all text and options are loaded from a notecard configuration file.
Now that you've mastered advanced communication, consider exploring:
Join Alife Virtual and start building advanced interactive experiences!