Skip to main content

C# SDK

The Obsidian C# SDK is a single file, Obsidian.cs, that handles TLS pinning, request signing, response verification, and HWID generation for you.

Drop the file into your project, add a NuGet reference to BouncyCastle, and you have a working client. Download the source from your app's SDK Integration tab in the dashboard. See Applications for where to grab it and how to rotate the secret it comes baked with.

Namespace and types

Everything lives in ObsidianSDK. The public surface is small.

namespace ObsidianSDK
{
public class ObsidianException : Exception { }

public class Obsidian
{
public string Session { get; }
public string Hwid { get; }
public bool Capture { get; set; }

public Obsidian(string apiKey, string apiSecret, string version);

public static string GetHwid();
public static string CaptureScreenshot();

public Dictionary<string, object> Init();
public Dictionary<string, object> Login(string username, string password);
public Dictionary<string, object> Register(string username, string password, string license);
public Dictionary<string, object> License(string license);
}
}

Every response comes back as a Dictionary<string, object> deserialized from the server JSON. Nested objects (like user or app) are themselves dictionaries, so you cast when you drill in.

Constructor

public Obsidian(string apiKey, string apiSecret, string version)

All three parameters are required and throw ArgumentException on null or empty. apiKey and apiSecret come from the dashboard. version must match the version configured on the app, otherwise the server returns a mismatch on Init.

See Applications for how version enforcement behaves when force_update is on.

The constructor computes the HWID synchronously through WMI plus a registry read, stores it on the Hwid property, and sets Capture to true.

info

The static constructor installs a global TLS handler on ServicePointManager that pins the SPKI SHA-256 of obsidianauth.com. Any other HttpWebRequest in your process that hits that host will also be pinned. Requests to other hosts are passed through unchanged.

Properties

Session is populated by Init() and sent as the session field on every subsequent call. Hwid is the 32-character lowercase hex fingerprint the SDK derives from your machine; both properties have private setters. Capture is the one knob you can flip: when true, Login, Register, and License include a base64 PNG screenshot; set it to false for headless or console builds.

See HWID Lock for how the server uses the HWID and when a mismatch triggers a 403.

Methods

Init

Dictionary<string, object> Init();

Calls POST /api/client/init with { api_key, version }. On success the returned dictionary carries:

  • success (bool)
  • session (string), which is also written to the Session property
  • app (dictionary) with name, version, and hwid_lock

Login, Register, and License call Init() automatically when Session is null, so calling it yourself up front is optional. Do it if you want to read the app block or fail fast at startup.

Register

Dictionary<string, object> Register(string username, string password, string license);

Creates an end user bound to a license key. The payload contains session, username, password, license, hwid, and screenshot.

After a successful register the SDK immediately calls Login(username, password) so last_login populates and downstream calls have a live session. Login failures inside this second call are swallowed; the register result is still returned. See the Changelog for the v1.1 behavior change.

On success the response has success, message, and user { username, level, expires_at }.

Login

Dictionary<string, object> Login(string username, string password);

Sends session, username, password, hwid, and screenshot. On success you get success, message, and user { username, level, expires_at }.

License

Dictionary<string, object> License(string license);

License-only authentication with no username or password. Payload: session, license, hwid, screenshot. On success: success, message, level, expires_at.

GetHwid (static)

public static string GetHwid();

Returns the same 32-character hex fingerprint the SDK uses internally. It reads, in order: HKLM\SOFTWARE\Microsoft\Cryptography\MachineGuid, Win32_ComputerSystemProduct.UUID, Win32_Processor.ProcessorId, and Win32_DiskDrive.SerialNumber. If every source fails, the fallback is obsidian-nohw-<hostname> hashed to the same 32 chars.

CaptureScreenshot (static)

public static string CaptureScreenshot();

Returns a base64 PNG of the virtual screen or an empty string on any failure. Dimensions come from GetSystemMetrics(78, 79), falling back to 1920x1080. Internally it uses System.Drawing.Bitmap with Graphics.CopyFromScreen.

Exceptions

Every failure path throws ObsidianException. The constructor is the one exception: null or empty inputs throw ArgumentException instead.

MessageWhen
network write: {inner}Failed writing the request body.
network: {inner}Failed reading the response and no HTTP response came back.
unsigned server response (refusing to trust)Response missing the X-Response-Signature header.
unknown server signing key '{kid}' (SDK expected 'k1')Server sent an X-Response-Kid the SDK does not recognize.
invalid server signature (bad base64)Signature header was not valid base64.
invalid server signature (response forged or tampered)Ed25519 verification failed.
malformed server responseBody was not parseable JSON.
Server detail stringAny non-2xx response. Passed through verbatim from the detail field.

Server error strings

These are the exact detail values raised by /api/client/* that surface as ObsidianException.Message. Match on them if you need branch-specific UX; otherwise show the message.

MessageEndpointHTTP
Application disabledinit, register, login, license403
Object detail with message ("Outdated client version. Update required."), latest_version, update_url, force_update (see note below)init426
Too many failed attempts, try again later.register, login429
License validation is temporarily paused for this application.register, license403
Access is temporarily paused for this application.login403
Invalid license keyregister, license400
License key is bannedregister, license403
License key already usedregister400
Username already takenregister400
Heartbeat session required. Call /heartbeat/start first.login, license403
License activation in progress, retry.license409
License not validlicense403
HWID mismatch. Key locked to another device.license403
License expiredlicense403
License state changed mid-activation, please retryregister409
Invalid username or passwordlogin401
warning

The 426 row is special: the server sends detail as a JSON object, not a string. The SDK's error path calls Dv.ToString() on the deserialized value, and for a Dictionary<string, object> that yields the .NET type-name string (System.Collections.Generic.Dictionary2[System.String,System.Object]), not the human-readable message. To read message, latest_version, update_url, and force_update, catch the exception and re-issue the request against /api/client/init` yourself, then inspect the raw response.

See Client Endpoints for the full request/response contract.

Framework and runtime requirements

The SDK targets classic .NET Framework, not .NET Core or .NET 5+.

RequirementNotes
Target framework.NET Framework 4.7.2 or newer. .NET Core, .NET 5, and .NET 6/7/8 are not supported because the SDK uses System.Web.Script.Serialization.JavaScriptSerializer, which was intentionally not ported to modern .NET.
PlatformWindows only. System.Management, Microsoft.Win32.Registry, and System.Drawing are Windows-specific.
CPUAnyCPU is fine. HWID probes work on both x86 and x64 Windows.
DependencyBouncyCastle, used for Ed25519 verification and X.509 SPKI extraction.
BCL referencesSystem.Web.Extensions (for JavaScriptSerializer), System.Management, System.Drawing.

The static constructor forces Tls12 | Tls11 | Tls on ServicePointManager and installs the SPKI-pinning validation callback. Because that callback is global, any other request in your process to obsidianauth.com will also be pinned. Other hosts are passed through.

csproj snippet

A minimal .NET Framework project referencing the SDK and its dependencies:

<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<OutputType>Exe</OutputType>
<RootNamespace>MyApp</RootNamespace>
<AssemblyName>MyApp</AssemblyName>
</PropertyGroup>

<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Management" />
<Reference Include="System.Web.Extensions" />
<Reference Include="BouncyCastle.Crypto">
<HintPath>packages\BouncyCastle.1.8.9\lib\BouncyCastle.Crypto.dll</HintPath>
</Reference>
</ItemGroup>

<ItemGroup>
<Compile Include="Obsidian.cs" />
<Compile Include="Program.cs" />
</ItemGroup>

<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

Install BouncyCastle:

nuget install BouncyCastle -Version 1.8.9 -OutputDirectory packages

Minimal usage

A typical username-and-password flow:

using System;
using System.Collections.Generic;
using ObsidianSDK;

class Program
{
static void Main()
{
var Obs = new Obsidian(
apiKey: "ak_your_key",
apiSecret: "sk_your_secret",
version: "1.0.0");

try
{
Obs.Init();
var Result = Obs.Login("alice", "hunter2");
var User = (Dictionary<string, object>)Result["user"];
Console.WriteLine("logged in as " + User["username"]);
}
catch (ObsidianException Ex)
{
Console.Error.WriteLine("auth failed: " + Ex.Message);
Environment.Exit(1);
}
}
}

For a console-only or headless build, turn the screenshot off before calling any auth method:

var Obs = new Obsidian("ak_your_key", "sk_your_secret", "1.0.0");
Obs.Capture = false;
Obs.License("XXXX-YYYY-ZZZZ-WWWW");

What the C# SDK does not do

There is no heartbeat client. If the app has require_heartbeat on, Login and License will 403 with Heartbeat session required. Call /heartbeat/start first. Use the C++ SDK for products that need continuous session liveness.

There is no verify_proof per-call receipt. Response integrity is enforced inside Post via the Ed25519 signature check on X-Response-Signature; there is no separate proof object exposed to callers.

There is no async API and no auto-retry. Every call blocks, and network faults throw on the first failure. Wrap in Task.Run yourself if you need to keep a UI thread responsive.