Java
Send WhatsApp messages from Java 8+ with the com.pingmate:pingmate-sdk client, via Maven or Gradle.
The Java client targets Java 8 and later and builds with Maven (3.8.3+) or Gradle (7.2+). Message variants are generated as typed model classes wrapped in SendMessageBody, so the type system enforces the right fields per message type.
Install
<dependency>
<groupId>com.pingmate</groupId>
<artifactId>pingmate-sdk</artifactId>
<version>0.1.0</version>
</dependency>implementation 'com.pingmate:pingmate-sdk:0.1.0'Distribution
If the artifact is not on a repository your build can reach, install it locally first with mvn clean install from the SDK source, then depend on the coordinates above.
Configure
Set the base path and your API key on the default client. Read the key from the environment, never hard-code it.
import com.pingmate.sdk.ApiClient;
import com.pingmate.sdk.Configuration;
import com.pingmate.sdk.api.MessagesApi;
import com.pingmate.sdk.api.BulkApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://pingmate.app");
client.setApiKey(System.getenv("PINGMATE_API_KEY")); // sent as the X-API-Key header
MessagesApi messages = new MessagesApi(client);
BulkApi bulk = new BulkApi(client);setBasePath defaults to http://localhost, so point it at 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, a recipient final String TO = "919876543210" (E.164 digits, country code, no leading +), and these imports:
import com.pingmate.sdk.ApiException;
import com.pingmate.sdk.model.*;
import java.util.List;Text
TextMessage msg = new TextMessage()
.messageType(TextMessage.MessageTypeEnum.TEXT)
.text("Hello from the Java 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.
TextMessage msg = new TextMessage()
.messageType(TextMessage.MessageTypeEnum.TEXT)
.text("Yes, your order is on the way.")
.contextId("wamid.HBgL...");
messages.sendMessage(new SendRequest().to(TO).message(new SendMessageBody(msg)));Image with caption
attachment is a public URL or a media ID from /api/v1/media/upload.
ImageMessage msg = new ImageMessage()
.messageType(ImageMessage.MessageTypeEnum.IMAGE)
.attachments(new Attachment()
.type(Attachment.TypeEnum.IMAGE)
.attachment("https://picsum.photos/600/400"))
.text("Photo of the day");
messages.sendMessage(new SendRequest().to(TO).message(new SendMessageBody(msg)));Video with caption
VideoMessage msg = new VideoMessage()
.messageType(VideoMessage.MessageTypeEnum.VIDEO)
.attachments(new Attachment()
.type(Attachment.TypeEnum.VIDEO)
.attachment("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.
DocumentMessage msg = new DocumentMessage()
.messageType(DocumentMessage.MessageTypeEnum.DOCUMENT)
.attachments(new Attachment()
.type(Attachment.TypeEnum.DOCUMENT)
.attachment("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
LocationMessage msg = new LocationMessage()
.messageType(LocationMessage.MessageTypeEnum.LOCATION)
.location(new Location()
.latitude("19.0760")
.longitude("72.8777")
.name("Mumbai Office")
.address("Bandra Kurla Complex, Mumbai"));
messages.sendMessage(new SendRequest().to(TO).message(new SendMessageBody(msg)));Contact card
ContactMessage 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.
ButtonsMessage msg = new ButtonsMessage()
.messageType(ButtonsMessage.MessageTypeEnum.BUTTONS)
.text("How would you like to proceed?")
.headerText("Order #12345")
.footerText("Reply within 24 hours")
.buttons(List.of(
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.
ButtonsMessage msg = new ButtonsMessage()
.messageType(ButtonsMessage.MessageTypeEnum.BUTTONS)
.text("Your order has shipped.")
.buttons(List.of(
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
InteractiveListMessage msg = new InteractiveListMessage()
.messageType(InteractiveListMessage.MessageTypeEnum.INTERACTIVE_LIST)
.text("Please select a service")
.headerText("Our Services")
.interactiveList(new InteractiveList()
.title("Select a service")
.sections(List.of(
new InteractiveListSection()
.title("Support")
.rows(List.of(
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(List.of(
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)));Carousel
A swipeable row of cards, each with its own image, text, and buttons. Up to ten cards.
CarouselMessage msg = new CarouselMessage()
.messageType(CarouselMessage.MessageTypeEnum.CAROUSEL)
.text("This week's bestsellers 🛍️")
.cards(List.of(
new CarouselCard()
.attachments(new Attachment().type(Attachment.TypeEnum.IMAGE).attachment("https://example.com/serum.jpg"))
.text("Aurora Serum, ₹1,299")
.buttons(List.of(
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()
.attachments(new Attachment().type(Attachment.TypeEnum.IMAGE).attachment("https://example.com/lip-tint.jpg"))
.text("Velvet Lip Tint, ₹699")
.buttons(List.of(
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.
TemplateMessage 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.
TemplateMessage tpl = new TemplateMessage()
.messageType(TemplateMessage.MessageTypeEnum.TEMPLATE)
.templateName("order_complete")
.templateLanguage("en_US")
.headerVariables(List.of("ORD-12345"))
.bodyVariables(List.of("John", "₹2,499", "March 15, 2026"))
.attachments(new Attachment()
.type(Attachment.TypeEnum.IMAGE)
.attachment("https://example.com/order-confirmation.jpg"));
messages.sendMessage(new SendRequest().to(TO).message(new SendMessageBody(tpl)));Schedule for later
Add scheduleTime to the request. The format is DD-MM-YYYY:HH-MM in IST, and the wallet is charged when you call. See Scheduling.
TextMessage msg = new TextMessage()
.messageType(TextMessage.MessageTypeEnum.TEXT)
.text("Reminder: your appointment is tomorrow at 10 AM.");
SendRequest req = new SendRequest()
.to(TO)
.scheduleTime("06-05-2026:18-15")
.message(new SendMessageBody(msg));
messages.sendMessage(req);Bulk send
One message (commonly a template) to up to 50 recipients, deduplicated server-side.
TemplateMessage tpl = new TemplateMessage()
.messageType(TemplateMessage.MessageTypeEnum.TEMPLATE)
.templateName("promotion_offer")
.templateLanguage("en_US")
.bodyVariables(List.of("30%", "March 31, 2026"));
BulkSendRequest req = new BulkSendRequest()
.to(List.of("919876543210", "919876543211", "919876543212"))
.message(new SendMessageBody(tpl));
BulkSendResponse res = bulk.sendBulkMessage(req);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.
import com.pingmate.sdk.api.MediaApi;
import java.io.File;
MediaApi mediaApi = new MediaApi(client);
File file = new File("catalog.pdf");
MediaUploadResponse upload = mediaApi.uploadMedia(file, "Spring Catalog", false);
String mediaId = upload.getData().getId();
DocumentMessage doc = new DocumentMessage()
.messageType(DocumentMessage.MessageTypeEnum.DOCUMENT)
.attachments(new Attachment()
.type(Attachment.TypeEnum.DOCUMENT)
.attachment(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.
BulkSendRequest req = new BulkSendRequest()
.to(List.of("919876543210", "919876543211", "919800000000"))
.message(new SendMessageBody(new TemplateMessage()
.messageType(TemplateMessage.MessageTypeEnum.TEMPLATE)
.templateName("promotion_offer")
.templateLanguage("en_US")
.bodyVariables(List.of("30%", "March 31, 2026"))));
BulkSendResponse res = bulk.sendBulkMessage(req);
for (BulkSendResponseDataResultsInner r : res.getData().getResults()) {
if (r.getError() != null) {
System.out.println("✗ " + r.getTo() + ": " + r.getError());
} else {
System.out.println("✓ " + r.getTo() + ": " + r.getId());
}
}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.
import com.pingmate.sdk.ApiException;
try {
messages.sendMessage(req);
} catch (ApiException e) {
if (e.getCode() == 402) {
throw new IllegalStateException("PingMate wallet is empty. Top up and retry.");
}
throw e;
}Reference
Every method and model is documented in the package's docs/ directory, alongside the API reference.
How is this guide?


