ASP.NET Core / C#
Use WhatsTig.Client (targets .NET 8 and .NET 10). A complete runnable example lives in the
repository at examples/dotnet-minimal-api.
dotnet add package WhatsTig.Client
Registration
using WhatsTig.Client;
builder.Services.AddWhatsTig(o =>
{
o.ApiKey = builder.Configuration["WhatsTig:ApiKey"]!; // dotnet user-secrets / Key Vault / env var
o.BaseUrl = new Uri(builder.Configuration["WhatsTig:BaseUrl"] ?? "https://api.whatstig.com");
o.Timeout = TimeSpan.FromSeconds(30);
o.MaxRetries = 2; // network errors, 429, 502-504; only for GETs and requests with an Idempotency-Key
});
Inject WhatsTigClient anywhere (it is a typed HttpClient, safe to use concurrently).
Instances
var instance = await wt.Instances.FindByExternalReferenceAsync("tenant-101")
?? await wt.Instances.CreateAsync(new CreateInstanceRequest("School A WhatsApp", "tenant-101"), idempotencyKey: "create-tenant-101");
await wt.Instances.ConnectAsync(instance.Id); // QR by default
var pairing = await wt.Instances.GetQrAsync(instance.Id); // pairing.QrDataUrl
var status = await wt.Instances.GetStatusAsync(instance.Id); // status.Status == "CONNECTED"
Pairing code instead of QR: ConnectAsync(id, new ConnectInstanceRequest("pairing_code", "+923001234567")).
Messages
var accepted = await wt.Messages.SendAsync(
SendMessageRequest.Text(instance.Id, "923001234567", "Your child was marked absent today.", clientReference: "attendance-987"),
idempotencyKey: "notification-987");
var message = await wt.Messages.GetAsync(accepted.Id);
var history = await wt.Messages.GetEventsAsync(accepted.Id);
await foreach (var failed in wt.Messages.ListAllAsync(new MessageQuery(Status: "FAILED", CreatedAfter: DateTimeOffset.UtcNow.AddDays(-1))))
Console.WriteLine($"{failed.Id} {failed.Failure?.Code}");
Media:
await using var file = File.OpenRead("report.pdf");
var media = await wt.Media.UploadAsync(file, "report.pdf", "application/pdf");
await wt.Messages.SendAsync(SendMessageRequest.Document(instance.Id, "923001234567", media.Id, "Report.pdf"));
CRM
Leads, deals, pipelines and companies (see the CRM API reference). Capture and convert are safe to retry: pass your own record ID as the idempotency key, or one is generated for you.
var captured = await wt.Leads.CaptureAsync(
new CreateLeadRequest(Phone: "923001234567", FirstName: "Sara", Email: "sara@example.com",
SourceDetail: new LeadSourceDetailInput(Utm: new Utm(Source: "google", Campaign: "spring"))),
idempotencyKey: "signup-42");
var lead = captured.Lead; // captured.Created == false if an open lead was updated
await wt.Leads.AssignAsync(lead.Id, new AssignLeadRequest("usr_...")); // null unassigns
await wt.Crm.AddNoteAsync(new CreateCrmNoteRequest("lead", lead.Id, "Asked for a demo on Friday"));
var result = await wt.Leads.ConvertAsync(lead.Id, new ConvertLeadRequest(
new ConvertLeadDeal(Title: "Annual plan", ValueMinor: 12_000_00, Currency: "USD")), idempotencyKey: $"convert-{lead.Id}");
await wt.Deals.MoveAsync(result.Deal.Id, new MoveDealRequest("st_..."));
await wt.Deals.CloseAsync(result.Deal.Id, CloseDealRequest.Won());
await foreach (var hot in wt.Leads.ListAllAsync(new LeadQuery(Status: "open", MinScore: 50, Sort: "score_desc")))
Console.WriteLine($"{hot.Name} {hot.Score} {hot.Owner?.Name}");
Money is in minor units (ValueMinor, cents). PATCH fields that accept null are Clearable<T>?: leave them
unset to keep the value, assign a value to change it, or assign Clearable<T>.Clear to clear it
(new UpdateLeadRequest(CompanyId: Clearable<string>.Clear, Version: lead.Version)). Pass Version to get a
409 instead of overwriting someone else's newer change.
Errors
try { await wt.Messages.SendAsync(request, key); }
catch (WhatsTigException e) when (e.Code == "INVALID_RECIPIENT") { /* fix the number */ }
catch (WhatsTigException e) when (e.Status == 429) { /* retry after e.RetryAfter with the same key */ }
Webhooks
app.MapPost("/whatstig/webhooks", async (HttpRequest request, IConfiguration config) =>
{
using var reader = new StreamReader(request.Body);
var raw = await reader.ReadToEndAsync(); // raw body; do not bind a model first
try
{
var evt = WebhookVerifier.Verify(raw, request.Headers[WebhookVerifier.SignatureHeader], config["WhatsTig:WebhookSecret"]!);
// dedupe on evt.Id; evt.Type; evt.ExternalReference; evt.Data (JsonElement)
return Results.Ok();
}
catch (WebhookVerificationException)
{
return Results.Unauthorized();
}
});
ABP Framework
Register in your module's ConfigureServices:
context.Services.AddWhatsTig(o =>
{
var cfg = context.Services.GetConfiguration();
o.ApiKey = cfg["WhatsTig:ApiKey"]!;
o.BaseUrl = new Uri(cfg["WhatsTig:BaseUrl"]!);
});
Call WhatsTIG from application services after your own permission checks, and send from background jobs
(IBackgroundJobManager) using your entity IDs as idempotency keys.