Skip to content

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.