Fix "Web request callback raised an exception" (NullReferenceException) in Oxide plugins
A callback passed to webrequest.Enqueue threw an exception, so Oxide logged it and dropped the result. The usual cause is code that reads response.Code or response.Body without checking them, or skips the status check entirely.
Web request callback raised an exception in 'ServerStatus v1.0.0' plugin (NullReferenceException: Object reference not set to an instance of an object)
Why it happens
Oxide calls your callback methods for webrequest.Enqueue on the main thread after the HTTP request finishes. The failure comes from your own code inside that callback, not from Oxide. The most common triggers:
response.Bodyis null on a failed request (DNS failure, timeout, refused connection). Indexing or parsing it throwsNullReferenceException.responseitself is null in some failure cases; callingresponse.Codedirectly throws.- A dependency (an item definition, a player, a config object) is null when the callback tries to use it, for example
ItemManager.FindItemDefinition("x").displayName. - The code assumes HTTP 200 and parses an error page or empty body from GitHub, the BattleMetrics API or your own web server.
The plugin stays loaded. Only the request that failed is lost, and the same callback keeps failing on every refresh until the body or status check is fixed.
How to fix it
- Find the callback in your plugin: the method name you passed to
webrequest.Enqueue. - First line of the callback, guard both objects:
if (response == null) { PrintWarning("No HTTP response"); return; }. - Check the status before parsing:
if (response.Code != 200) { PrintWarning("HTTP " + response.Code); return; }. - Check the body:
if (string.IsNullOrEmpty(response.Body)) { PrintWarning("Empty body"); return; }. Parse only after this. - Null-check anything you use inside the parsed data, including the item definition or player you look up later.
- Save the file, reload with
oxide.reload ServerStatus, and watch the console. A successful request logs nothing unusual; a failed one now prints your warning instead of the exception line. - To confirm, temporarily point the URL at an unreachable host and check the callback exits with your warning.
Before and after
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
namespace Oxide.Plugins
{
[Info("ServerStatus", "RustPluginGenerator", "1.0.0")]
[Description("Shows a status line from a JSON endpoint in chat.")]
public class ServerStatus : RustPlugin
{
private const string StatusUrl = "https://api.example.com/server/status.json";
private Timer statusTimer;
private class StatusResponse
{
[JsonProperty("players")] public int Players { get; set; }
[JsonProperty("msg")] public string Message { get; set; }
}
private void OnServerInitialized()
{
Refresh();
statusTimer = timer.Every(120f, Refresh);
}
private void Unload()
{
statusTimer?.Destroy();
}
private void Refresh()
{
webrequest.Enqueue(StatusUrl, null, (code, response) =>
{
// No null check on response, none on Body, no code check.
if (response.Code == 200)
{
var data = JsonConvert.DeserializeObject<StatusResponse>(response.Body);
Server.Broadcast(string.Format("Players: {0} - {1}", data.Players, data.Message));
}
else
{
Interface.Oxide.LogWarning("Status endpoint returned " + response.Code);
}
}, this);
}
}
}using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
namespace Oxide.Plugins
{
[Info("ServerStatus", "RustPluginGenerator", "1.0.1")]
[Description("Shows a status line from a JSON endpoint in chat.")]
public class ServerStatus : RustPlugin
{
private const string StatusUrl = "https://api.example.com/server/status.json";
private Timer statusTimer;
private class StatusResponse
{
[JsonProperty("players")] public int Players { get; set; }
[JsonProperty("msg")] public string Message { get; set; }
}
private void OnServerInitialized()
{
Refresh();
statusTimer = timer.Every(120f, Refresh);
}
private void Unload()
{
if (statusTimer != null)
{
statusTimer.Destroy();
statusTimer = null;
}
}
private void Refresh()
{
webrequest.Enqueue(StatusUrl, null, OnStatusReceived, this);
}
private void OnStatusReceived(int code, string body)
{
if (code != 200)
{
Interface.Oxide.LogWarning("Status endpoint returned HTTP " + code);
return;
}
if (string.IsNullOrEmpty(body))
{
Interface.Oxide.LogWarning("Status endpoint returned an empty body");
return;
}
StatusResponse data = null;
try
{
data = JsonConvert.DeserializeObject<StatusResponse>(body);
}
catch (Exception ex)
{
Interface.Oxide.LogWarning("Status JSON could not be parsed: " + ex.Message);
return;
}
if (data == null)
{
Interface.Oxide.LogWarning("Status JSON was empty after parsing");
return;
}
Server.Broadcast(string.Format("Players: {0} - {1}", data.Players, data.Message ?? "no message"));
}
}
}FAQ
Does this error unload my plugin or stop other plugins?
No. Oxide catches the exception, prints the line, and keeps the plugin loaded. Only that web request result is lost, so the rest of the plugin keeps working. Repeated exceptions usually mean every refresh cycle fails the same way.
Why do I only see the HTTP host and not the callback name?
The line names the plugin and version ('ServerStatus v1.0.0') but not the callback method. Search the plugin for webrequest and inspect every callback you pass to Enqueue. If there is only one request, the callback is that one.
Is response null, or just response.Body?
Both can happen. Enqueue passes null when no HTTP response was produced at all, and Body is empty or null for statuses without content or on connection failures. Guarding both on the first lines covers either case.
Why does it fail when my browser loads the same URL fine?
The browser sends a different User-Agent, follows redirects and retries. The server may return 403 or 429 to the plugin, or block the datacenter IP. Log response.Code in the callback to see what the server actually sent back.
Different error?
Paste the full console output and get the cause and a fix in plain English.
Open the error explainerSkip the debugging
Generated plugins are compiled before you get them, and compiler errors are fixed automatically.
Generate a plugin2 free plugins a month, no card.
Last updated Oct 26, 2026.
Get plugins that compile first time
Generate