A Wrapper for Whatsapp Business Cloud API hosted by Meta.
Official API Documentation: Meta for Developers
Sell Product and Services: Product Message Configuration
Account Metrics: Account Metrics Configuration for analytics and conversation analytics
QR Code Message Management: QR Code Messages for WhatsApp Business
Webhook Configuration Documentation: WhatsApp Cloud API Webhook
WhatsApp Cloud API Error Codes: Error Codes
Take note: Sending a message to a phone number format 00[Country Code] xx xx xx
using the prefix 00
before the country code will make the cloud API to return invalid parameter error (#100) (Credits @Tekkharibo)
Note: This package is WIP. The capabilities of Cloud API will be reflected soon. Feel free to contribute!
-
Sending Messages
- Text
- Media (image, video, audio, document, sticker)
- Contact
- Location
- Interactive (List, Reply)
- Template
- Template Messages with parameters
- Single Product Message
- Multiple Product Message
-
Receiving Message (via Webhook)
- Text
- Media (image, video, audio, document, sticker)
- Contact
- Location
- Interactive (List, Reply)
- Button
-
WhatsApp Business Management API
- QR Code Message Management
- Account Metrics
- PackageManager:
PM> Install-Package WhatsappBusiness.CloudApi
- DotNetCLI:
> dotnet add package WhatsappBusiness.CloudApi
Before you proceed kindly aquaint yourself with WhatsApp Business Cloud Apis by going through the Docs in Meta's developer portal if you like.
-
Obtain Temporary access token for meta developers portal.
-
Ensure your project is running on the minimun supported versions of .Net
-
WhatsAppBusinessCloudAPi is dependency injection (DI) friendly and can be readily injected into your classes. You can read more on DI in Asp.Net core here. If you can't use DI you can always manually create a new instance of WhatsAppBusinessClient and pass in an httpClient instance in it's constructor. eg.
// When Dependency Injection is not possible...
//create httpclient instance
var httpClient = new HttpClient();
httpClient.BaseAddress = WhatsAppBusinessRequestEndpoint.BaseAddress;
//create WhatsAppBusiness API client instance
var whatsAppBusinessClient = new WhatsAppBusinessClient(httpClient, whatsAppConfig); //make sure to pass httpclient and whatsAppConfig intance as an argument
I would recommend creating WhatsAppBusinessClient using Dependency Injection. [Optional] You can use any IOC container or Microsoft DI container in your legacy projects.
// Adding Dependency Injection into legacy projects
public static IServiceProvider ServiceProvider;
// To be used in the main application startup method
void Application_Start(object sender, EventArgs e)
{
var hostBuilder = new HostBuilder();
hostBuilder.ConfigureServices(ConfigureServices);
var host = hostBuilder.Build();
ServiceProvider = host.Services;
}
void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<IWhatsAppBusinessClient, WhatsAppBusinessClient>(options => options.BaseAddress = WhatsAppBusinessRequestEndpoint.BaseAddress);
//inject services here
}
- Install WhatsappBusiness.CloudApi Project via Nuget Package Manager Console or Nuget Package Manager GUI.
- In Program.cs add the namespace...
using WhatsappBusiness.CloudApi.Configurations;
using WhatsappBusiness.CloudApi.Extensions;
- Inside ConfigureServices method add the following
WhatsAppBusinessCloudApiConfig whatsAppConfig = new WhatsAppBusinessCloudApiConfig();
whatsAppConfig.WhatsAppBusinessPhoneNumberId = builder.Configuration.GetSection("WhatsAppBusinessCloudApiConfiguration")["WhatsAppBusinessPhoneNumberId"];
whatsAppConfig.WhatsAppBusinessAccountId = builder.Configuration.GetSection("WhatsAppBusinessCloudApiConfiguration")["WhatsAppBusinessAccountId"];
whatsAppConfig.WhatsAppBusinessId = builder.Configuration.GetSection("WhatsAppBusinessCloudApiConfiguration")["WhatsAppBusinessId"];
whatsAppConfig.AccessToken = builder.Configuration.GetSection("WhatsAppBusinessCloudApiConfiguration")["AccessToken"];
builder.Services.AddWhatsAppBusinessCloudApiService(whatsAppConfig);
- Once the WhatsAppBusinessClient is registered, you can pass it and use it in your classes to make API calls to WhatsApp Business Cloud API Server as follows;
public class SendMessageController
{
private readonly IWhatsAppBusinessClient _whatsAppBusinessClient;
public SendMessageController(IWhatsAppBusinessClient whatsAppBusinessClient)
{
_whatsAppBusinessClient = whatsAppBusinessClient;
}
....
//code omitted for brevity
}
TextMessageRequest textMessageRequest = new TextMessageRequest();
textMessageRequest.To = "Recipient Phone Number";
textMessageRequest.Text = new WhatsAppText();
textMessageRequest.Text.Body = "Message Body";
textMessageRequest.Text.PreviewUrl = false;
var results = await _whatsAppBusinessClient.SendTextMessageAsync(textMessageRequest);
AudioMessageByUrlRequest audioMessage = new AudioMessageByUrlRequest();
audioMessage.To = "Recipient Phone Number";
audioMessage.Audio = new MediaAudioUrl();
audioMessage.Audio.Link = "Audio Url";
var results = await _whatsAppBusinessClient.SendAudioAttachmentMessageByUrlAsync(audioMessage);
DocumentMessageByUrlRequest documentMessage = new DocumentMessageByUrlRequest();
documentMessage.To = "Recipient Phone Number";
documentMessage.Document = new MediaDocumentUrl();
documentMessage.Document.Link = "Document Url";
var results = await _whatsAppBusinessClient.SendDocumentAttachmentMessageByUrlAsync(documentMessage);
ImageMessageByUrlRequest imageMessage = new ImageMessageByUrlRequest();
imageMessage.To = "Recipient Phone Number";
imageMessage.Image = new MediaImageUrl();
imageMessage.Image.Link = "Image Url";
var results = await _whatsAppBusinessClient.SendImageAttachmentMessageByUrlAsync(imageMessage);
StickerMessageByUrlRequest stickerMessage = new StickerMessageByUrlRequest();
stickerMessage.To = "Recipient Phone Number";
stickerMessage.Sticker = new MediaStickerUrl();
stickerMessage.Sticker.Link = "Sticker Url";
var results = await _whatsAppBusinessClient.SendStickerMessageByUrlAsync(stickerMessage);
VideoMessageByUrlRequest videoMessage = new VideoMessageByUrlRequest();
videoMessage.To = "Recipient Phone Number";
videoMessage.Video = new MediaVideoUrl();
videoMessage.Video.Link = "Video url";
var results = await _whatsAppBusinessClient.SendVideoAttachmentMessageByUrlAsync(videoMessage);
ContactMessageRequest contactMessageRequest = new ContactMessageRequest();
contactMessageRequest.To = "Recipient Phone Number";
contactMessageRequest.Contacts = new List<ContactData>()
{
new ContactData()
{
Addresses = new List<Address>()
{
new Address()
{
State = "State Test",
City = "City Test",
Zip = "Zip Test",
Country = "Country Test",
CountryCode = "Country Code Test",
Type = "Home"
}
},
Name = new Name()
{
FormattedName = "Testing name",
FirstName = "FName",
LastName = "LName",
MiddleName = "MName"
}
}
};
var results = await _whatsAppBusinessClient.SendContactAttachmentMessageAsync(contactMessageRequest);
LocationMessageRequest locationMessageRequest = new LocationMessageRequest();
locationMessageRequest.To = "Recipient Phone Number";
locationMessageRequest.Location = new Location();
locationMessageRequest.Location.Name = "Location Test";
locationMessageRequest.Location.Address = "Address Test";
locationMessageRequest.Location.Longitude = "location longitude";
locationMessageRequest.Location.Latitude = "location latitude";
var results = await _whatsAppBusinessClient.SendLocationMessageAsync(locationMessageRequest);
InteractiveListMessageRequest interactiveListMessage = new InteractiveListMessageRequest();
interactiveListMessage.To = "Recipient Phone Number";
interactiveListMessage.Interactive = new InteractiveListMessage();
interactiveListMessage.Interactive.Header = new Header();
interactiveListMessage.Interactive.Header.Type = "text";
interactiveListMessage.Interactive.Header.Text = "List Header Sample Test";
interactiveListMessage.Interactive.Body = new ListBody();
interactiveListMessage.Interactive.Body.Text = "List Message Body";
interactiveListMessage.Interactive.Footer = new Footer();
interactiveListMessage.Interactive.Footer.Text = "List Footer Sample Test";
interactiveListMessage.Interactive.Action = new ListAction();
interactiveListMessage.Interactive.Action.Button = "Send";
interactiveListMessage.Interactive.Action.Sections = new List<Section>()
{
new Section()
{
Title = "Category A",
Rows = new List<Row>()
{
new Row()
{
Id = "Item_A1",
Title = "Apples",
Description = "Enjoy fruits for free"
},
new Row()
{
Id = "Item_A2",
Title = "Tangerines",
Description = "Enjoy fruits for free"
},
},
},
new Section()
{
Title = "Category B",
Rows = new List<Row>()
{
new Row()
{
Id = "Item_B1",
Title = "2JZ",
Description = "Engine discounts"
},
new Row()
{
Id = "Item_2",
Title = "1JZ",
Description = "Engine discounts"
},
}
}
};
var results = await _whatsAppBusinessClient.SendInteractiveListMessageAsync(interactiveListMessage);
InteractiveReplyButtonMessageRequest interactiveReplyButtonMessage = new InteractiveReplyButtonMessageRequest();
interactiveReplyButtonMessage.To = "Recipient Phone Number";
interactiveReplyButtonMessage.Interactive = new InteractiveReplyButtonMessage();
interactiveReplyButtonMessage.Interactive.Body = new ReplyButtonBody();
interactiveReplyButtonMessage.Interactive.Body.Text = "Reply Button Body";
interactiveReplyButtonMessage.Interactive.Action = new ReplyButtonAction();
interactiveReplyButtonMessage.Interactive.Action.Buttons = new List<ReplyButton>()
{
new ReplyButton()
{
Type = "reply",
Reply = new Reply()
{
Id = "SAMPLE_1_CLICK",
Title = "CLICK ME!!!"
}
},
new ReplyButton()
{
Type = "reply",
Reply = new Reply()
{
Id = "SAMPLE_2_CLICK",
Title = "LATER"
}
}
};
var results = await _whatsAppBusinessClient.SendInteractiveReplyButtonMessageAsync(interactiveReplyButtonMessage);
TextTemplateMessageRequest textTemplateMessage = new TextTemplateMessageRequest();
textTemplateMessage.To = "Recipient Phone Number";
textTemplateMessage.Template = new TextMessageTemplate();
textTemplateMessage.Template.Name = "Template Name";
textTemplateMessage.Template.Language = new TextMessageLanguage();
textTemplateMessage.Template.Language.Code = "en_US";
var results = await _whatsAppBusinessClient.SendTextMessageTemplateAsync(textTemplateMessage);
// For Text Template message with parameters supported component type is body only
TextTemplateMessageRequest textTemplateMessage = new TextTemplateMessageRequest();
textTemplateMessage.To = sendTemplateMessageViewModel.RecipientPhoneNumber;
textTemplateMessage.Template = new TextMessageTemplate();
textTemplateMessage.Template.Name = sendTemplateMessageViewModel.TemplateName;
textTemplateMessage.Template.Language = new TextMessageLanguage();
textTemplateMessage.Template.Language.Code = LanguageCode.English_US;
textTemplateMessage.Template.Components = new List<TextMessageComponent>();
textTemplateMessage.Template.Components.Add(new TextMessageComponent()
{
Type = "body",
Parameters = new List<TextMessageParameter>()
{
new TextMessageParameter()
{
Type = "text",
Text = "Testing Parameter Placeholder Position 1"
},
new TextMessageParameter()
{
Type = "text",
Text = "Testing Parameter Placeholder Position 2"
}
}
});
var results = await _whatsAppBusinessClient.SendTextMessageTemplateAsync(textTemplateMessage);
// Tested with facebook predefined template name: sample_movie_ticket_confirmation
ImageTemplateMessageRequest imageTemplateMessage = new ImageTemplateMessageRequest();
imageTemplateMessage.To = sendTemplateMessageViewModel.RecipientPhoneNumber;
imageTemplateMessage.Template = new ImageMessageTemplate();
imageTemplateMessage.Template.Name = sendTemplateMessageViewModel.TemplateName;
imageTemplateMessage.Template.Language = new ImageMessageLanguage();
imageTemplateMessage.Template.Language.Code = LanguageCode.English_US;
imageTemplateMessage.Template.Components = new List<ImageMessageComponent>()
{
new ImageMessageComponent()
{
Type = "header",
Parameters = new List<ImageMessageParameter>()
{
new ImageMessageParameter()
{
Type = "image",
Image = new Image()
{
Link = "https://otakukart.com/wp-content/uploads/2022/03/Upcoming-Marvel-Movies-In-2022-23.jpg"
}
}
},
},
new ImageMessageComponent()
{
Type = "body",
Parameters = new List<ImageMessageParameter>()
{
new ImageMessageParameter()
{
Type = "text",
Text = "Movie Testing"
},
new ImageMessageParameter()
{
Type = "date_time",
DateTime = new ImageTemplateDateTime()
{
FallbackValue = DateTime.Now.ToString("dddd d, yyyy"),
DayOfWeek = (int)DateTime.Now.DayOfWeek,
Year = DateTime.Now.Year,
Month = DateTime.Now.Month,
DayOfMonth = DateTime.Now.Day,
Hour = DateTime.Now.Hour,
Minute = DateTime.Now.Minute,
Calendar = "GREGORIAN"
}
},
new ImageMessageParameter()
{
Type = "text",
Text = "Venue Test"
},
new ImageMessageParameter()
{
Type = "text",
Text = "Seat 1A, 2A, 3A and 4A"
}
}
}
};
var results = await _whatsAppBusinessClient.SendImageAttachmentTemplateMessageAsync(imageTemplateMessage);
// Tested with facebook predefined template name: sample_issue_resolution
InteractiveTemplateMessageRequest interactiveTemplateMessage = new InteractiveTemplateMessageRequest();
interactiveTemplateMessage.To = sendTemplateMessageViewModel.RecipientPhoneNumber;
interactiveTemplateMessage.Template = new InteractiveMessageTemplate();
interactiveTemplateMessage.Template.Name = sendTemplateMessageViewModel.TemplateName;
interactiveTemplateMessage.Template.Language = new InteractiveMessageLanguage();
interactiveTemplateMessage.Template.Language.Code = LanguageCode.English_US;
interactiveTemplateMessage.Template.Components = new List<InteractiveMessageComponent>();
interactiveTemplateMessage.Template.Components.Add(new InteractiveMessageComponent()
{
Type = "body",
Parameters = new List<InteractiveMessageParameter>()
{
new InteractiveMessageParameter()
{
Type = "text",
Text = "Interactive Parameter Placeholder Position 1"
}
}
});
var results = await _whatsAppBusinessClient.SendInteractiveTemplateMessageAsync(interactiveTemplateMessage);
First you need to setup callback url and verify token string for WhatsApp Cloud API to verify your callback url. Verification part
[HttpGet("<YOUR ENDPOINT ROUTE>")]
public ActionResult<string> ConfigureWhatsAppMessageWebhook([FromQuery(Name = "hub.mode")] string hubMode,
[FromQuery(Name = "hub.challenge")] int hubChallenge,
[FromQuery(Name = "hub.verify_token")] string hubVerifyToken)
{
return Ok(hubChallenge);
}
[HttpPost("<YOUR ENDPOINT ROUTE>")]
public IActionResult ReceiveWhatsAppTextMessage([FromBody] dynamic messageReceived)
{
// Logic to handle different type of messages received
return Ok();
}
TextMessageReplyRequest textMessageReplyRequest = new TextMessageReplyRequest();
textMessageReplyRequest.Context = new TextMessageContext();
textMessageReplyRequest.Context.MessageId = textMessage.SingleOrDefault().Id;
textMessageReplyRequest.To = textMessage.SingleOrDefault().From;
textMessageReplyRequest.Text = new WhatsAppText();
textMessageReplyRequest.Text.Body = "Your Message was received. Processing the request shortly";
textMessageReplyRequest.Text.PreviewUrl = false;
await _whatsAppBusinessClient.SendTextMessageAsync(textMessageReplyRequest);
[HttpPost]
public async Task<IActionResult> GetMessage()
{
string stringifiedBody;
string xHubSignature256 = this.HttpContext.Request.Headers["X-Hub-Signature-256"].ToString();
using (var sr = new StreamReader(this.HttpContext.Request.Body))
{
stringifiedBody = await sr.ReadToEndAsync().ConfigureAwait(false);
}
string xHubSignature256Result = FacebookWebhookHelper.CalculateSignature(this._configuration.GetSection("WhatsAppBusinessCloudApiConfiguration")["AppSecret"], stringifiedBody);
if (!String.Equals(xHubSignature256, xHubSignature256Result, StringComparison.InvariantCultureIgnoreCase))
return this.Unauthorized("Invalid Signature");
return this.Ok();
}
WhatsAppBusinessClient Throws WhatsappBusinessCloudAPIException
It is your role as the developer to catch
the exception and continue processing in your aplication. Snippet below shows how you can catch the WhatsappBusinessCloudAPIException.
using WhatsappBusiness.CloudApi.Exceptions; // add this to you class or namespace
try
{
return await _whatsAppBusinessClient.SendTextMessageAsync(textMessageRequest);
}
catch (WhatsappBusinessCloudAPIException ex)
{
_logger.LogError(ex, ex.Message);
}
If you will face any issue with the usage of this package please raise one so as we can quickly fix it as soon as possible.
This is an opensource project under MIT License
so any one is welcome to contribute from typo, to source code to documentation.
- Gabriel
- All other contributors