WhatsApp API Platform

C# (Legacy)

The RestSharp build of the Pingmate.Sdk C# client, for .NET Framework, older .NET Core, Mono, and Xamarin.

The legacy build is the same Pingmate.Sdk client compiled for older runtimes. It targets .NET Standard 2.0 and uses RestSharp for HTTP instead of HttpClient. The namespace, models, enums, and method signatures are identical to the modern C# build, so the code below is the same on both; only install and transport differ.

Which build?

On .NET 8 and later, use the modern C# build. Reach for this one only when you are pinned to .NET Framework 4.6.1+, .NET Core 2.0+, Mono, or Xamarin.

Install

Both builds publish under the id Pingmate.Sdk. This one is the RestSharp, .NET Standard 2.0 assembly.

dotnet add package Pingmate.Sdk

Distribution

The package is at 0.1.0. If your feed does not resolve the netstandard2.0 assembly automatically on your target framework, reference the legacy build artifact you were given.

Its dependencies are RestSharp, Json.NET, JsonSubTypes, and System.ComponentModel.Annotations.

Configure

Set the base path and add your API key under the X-API-Key scheme. Read the key from the environment, never hard-code it.

using Pingmate.Sdk.Api;
using Pingmate.Sdk.Client;
using Pingmate.Sdk.Model;

var config = new Configuration { BasePath = "https://pingmate.app" };
config.AddApiKey("X-API-Key", Environment.GetEnvironmentVariable("PINGMATE_API_KEY"));

var messages = new MessagesApi(config);
var bulk = new BulkApi(config);

BasePath defaults to http://localhost, so set it to your PingMate host (or your white-label domain).

Examples

Every message type flows through the same SendMessage call. You build the variant, wrap it in new SendMessageBody(variant), and attach it to a SendRequest. The examples below assume the messages client and a recipient var to = "919876543210"; (E.164 digits, country code, no leading +).

Text

var msg = new TextMessage(
    messageType: TextMessage.MessageTypeEnum.Text,
    text: "Hello from the C# SDK 👋");

messages.SendMessage(new SendRequest(to: to, message: new SendMessageBody(msg)));

Reply in a thread

Set contextId to the ID of the message you are replying to.

var msg = new TextMessage(
    contextId: "wamid.HBgL...",
    messageType: TextMessage.MessageTypeEnum.Text,
    text: "Yes, your order is on the way.");

messages.SendMessage(new SendRequest(to: to, message: new SendMessageBody(msg)));

Image with caption

varAttachment is a public URL or a media ID from /api/v1/media/upload.

var msg = new ImageMessage(
    messageType: ImageMessage.MessageTypeEnum.Image,
    attachments: new Attachment(type: Attachment.TypeEnum.Image, varAttachment: "https://picsum.photos/600/400"),
    text: "Photo of the day");

messages.SendMessage(new SendRequest(to: to, message: new SendMessageBody(msg)));

Video with caption

var msg = new VideoMessage(
    messageType: VideoMessage.MessageTypeEnum.Video,
    attachments: new Attachment(type: Attachment.TypeEnum.Video, varAttachment: "https://example.com/demo.mp4"),
    text: "A 30-second product tour");

messages.SendMessage(new SendRequest(to: to, message: new SendMessageBody(msg)));

Document

fileName sets the display name the recipient sees.

var msg = new DocumentMessage(
    messageType: DocumentMessage.MessageTypeEnum.Document,
    attachments: new Attachment(
        type: Attachment.TypeEnum.Document,
        varAttachment: "https://example.com/invoice.pdf",
        fileName: "Invoice-2026-001.pdf"),
    text: "Here is your invoice");

messages.SendMessage(new SendRequest(to: to, message: new SendMessageBody(msg)));

Location

var msg = new LocationMessage(
    messageType: LocationMessage.MessageTypeEnum.Location,
    location: new Location(
        latitude: "19.0760",
        longitude: "72.8777",
        address: "Bandra Kurla Complex, Mumbai",
        name: "Mumbai Office"));

messages.SendMessage(new SendRequest(to: to, message: new SendMessageBody(msg)));

Contact card

var msg = new ContactMessage(
    messageType: ContactMessage.MessageTypeEnum.Contact,
    contact: new Contact(name: "PingMate Support", phoneNumber: "919876500000", organization: "PingMate"));

messages.SendMessage(new SendRequest(to: to, message: new SendMessageBody(msg)));

Quick-reply buttons

Up to three tappable buttons. The buttonPayload comes back to your webhook on tap.

var msg = new ButtonsMessage(
    messageType: ButtonsMessage.MessageTypeEnum.Buttons,
    text: "How would you like to proceed?",
    headerText: "Order #12345",
    footerText: "Reply within 24 hours",
    buttons: new List<Button>
    {
        new Button(buttonType: Button.ButtonTypeEnum.Text, buttonText: "Confirm", buttonPayload: "confirm_order"),
        new Button(buttonType: Button.ButtonTypeEnum.Text, buttonText: "Cancel", buttonPayload: "cancel_order"),
    });

messages.SendMessage(new SendRequest(to: to, message: new SendMessageBody(msg)));

Call-to-action buttons

Url and Call buttons open a link or dial a number instead of replying.

var msg = new ButtonsMessage(
    messageType: ButtonsMessage.MessageTypeEnum.Buttons,
    text: "Your order has shipped.",
    buttons: new List<Button>
    {
        new Button(buttonType: Button.ButtonTypeEnum.Url, buttonText: "Track order", buttonPayload: "https://example.com/track/12345"),
        new Button(buttonType: Button.ButtonTypeEnum.Call, buttonText: "Call support", buttonPayload: "919876500000"),
    });

messages.SendMessage(new SendRequest(to: to, message: new SendMessageBody(msg)));

Interactive list

var msg = new InteractiveListMessage(
    messageType: InteractiveListMessage.MessageTypeEnum.InteractiveList,
    text: "Please select a service",
    headerText: "Our Services",
    interactiveList: new InteractiveList(
        title: "Select a service",
        sections: new List<InteractiveListSection>
        {
            new InteractiveListSection(title: "Support", rows: new List<InteractiveListRow>
            {
                new InteractiveListRow(id: "billing", title: "Billing Support", description: "Invoices and payments"),
                new InteractiveListRow(id: "technical", title: "Technical Support", description: "Product issues"),
            }),
            new InteractiveListSection(title: "Sales", rows: new List<InteractiveListRow>
            {
                new InteractiveListRow(id: "demo", title: "Request a Demo"),
                new InteractiveListRow(id: "pricing", title: "Get Pricing"),
            }),
        }));

messages.SendMessage(new SendRequest(to: to, message: new SendMessageBody(msg)));

A swipeable row of cards, each with its own image, text, and buttons. Up to ten cards.

var msg = new CarouselMessage(
    messageType: CarouselMessage.MessageTypeEnum.Carousel,
    text: "This week's bestsellers 🛍️",
    cards: new List<CarouselCard>
    {
        new CarouselCard(
            text: "Aurora Serum, ₹1,299",
            attachments: new Attachment(type: Attachment.TypeEnum.Image, varAttachment: "https://example.com/serum.jpg"),
            buttons: new List<Button>
            {
                new Button(buttonType: Button.ButtonTypeEnum.Url, buttonText: "View", buttonPayload: "https://example.com/p/serum"),
                new Button(buttonType: Button.ButtonTypeEnum.Text, buttonText: "Add to cart", buttonPayload: "add_serum"),
            }),
        new CarouselCard(
            text: "Velvet Lip Tint, ₹699",
            attachments: new Attachment(type: Attachment.TypeEnum.Image, varAttachment: "https://example.com/lip-tint.jpg"),
            buttons: new List<Button>
            {
                new Button(buttonType: Button.ButtonTypeEnum.Url, buttonText: "View", buttonPayload: "https://example.com/p/lip-tint"),
                new Button(buttonType: Button.ButtonTypeEnum.Text, buttonText: "Add to cart", buttonPayload: "add_tint"),
            }),
    });

messages.SendMessage(new SendRequest(to: to, message: new SendMessageBody(msg)));

Template (basic)

Templates are the only message type you can send to open a new conversation. The template must be approved first.

var tpl = new TemplateMessage(
    messageType: TemplateMessage.MessageTypeEnum.Template,
    templateName: "hello_world",
    templateLanguage: "en_US");

messages.SendMessage(new SendRequest(to: to, message: new SendMessageBody(tpl)));

Template with media header and variables

headerVariables and bodyVariables fill the {{1}}, {{2}} placeholders in order.

var tpl = new TemplateMessage(
    messageType: TemplateMessage.MessageTypeEnum.Template,
    templateName: "order_complete",
    templateLanguage: "en_US",
    headerVariables: new List<string> { "ORD-12345" },
    bodyVariables: new List<string> { "John", "₹2,499", "March 15, 2026" },
    attachments: new Attachment(type: Attachment.TypeEnum.Image, varAttachment: "https://example.com/order-confirmation.jpg"));

messages.SendMessage(new SendRequest(to: to, message: new SendMessageBody(tpl)));

Schedule for later

Pass scheduleTime to the request. The format is DD-MM-YYYY:HH-MM in IST, and the wallet is charged when you call. See Scheduling.

var msg = new TextMessage(messageType: TextMessage.MessageTypeEnum.Text, text: "Your festive offer is live.");

messages.SendMessage(new SendRequest(
    to: to,
    scheduleTime: "25-12-2026:09-30",
    message: new SendMessageBody(msg)));

Bulk send

One message (commonly a template) to up to 50 recipients, deduplicated server-side.

var tpl = new TemplateMessage(
    messageType: TemplateMessage.MessageTypeEnum.Template,
    templateName: "promotion_offer",
    templateLanguage: "en_US",
    bodyVariables: new List<string> { "30%", "March 31, 2026" });

bulk.SendBulkMessage(new BulkSendRequest(
    to: new List<string> { "919876543210", "919876543211", "919876543212" },
    message: new SendMessageBody(tpl)));

Workflows

These combine the calls above into the patterns you will actually ship.

Upload media, then send it

Upload large or reused files once. UploadMedia returns an Id you reference from any media message instead of a public URL. Limits: images 5 MB, video and audio 16 MB, PDF 100 MB.

var mediaApi = new MediaApi(config);

var upload = mediaApi.UploadMedia(new FileParameter("catalog.pdf", System.IO.File.OpenRead("catalog.pdf")));
var mediaId = upload.Data.Id;

var doc = new DocumentMessage(
    messageType: DocumentMessage.MessageTypeEnum.Document,
    attachments: new Attachment(type: Attachment.TypeEnum.Document, varAttachment: mediaId, fileName: "Spring Catalog.pdf"),
    text: "Our spring catalog is here 📖");

messages.SendMessage(new SendRequest(to: to, message: new SendMessageBody(doc)));

Bulk campaign with per-recipient results

SendBulkMessage returns one result per recipient. Each carries either an Id (queued) or an Error, so you can log or retry the failures.

var req = new BulkSendRequest(
    to: new List<string> { "919876543210", "919876543211", "919800000000" },
    message: new SendMessageBody(new TemplateMessage(
        messageType: TemplateMessage.MessageTypeEnum.Template,
        templateName: "promotion_offer",
        templateLanguage: "en_US",
        bodyVariables: new List<string> { "30%", "March 31, 2026" })));

var res = bulk.SendBulkMessage(req);
foreach (var r in res.Data.Results)
{
    if (r.Error != null)
        Console.WriteLine($"✗ {r.To}: {r.Error}");
    else
        Console.WriteLine($"✓ {r.To}: {r.Id}");
}

Resilient sending

A send returns 402 Payment Required when the wallet is empty or the plan is inactive. Catch it and surface a clear action instead of crashing the job.

try
{
    messages.SendMessage(request);
}
catch (ApiException e) when (e.ErrorCode == 402)
{
    throw new InvalidOperationException("PingMate wallet is empty. Top up and retry.");
}

Reference

Every method and model is documented in the package's docs/ directory, alongside the API reference.

How is this guide?

On this page