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
WhatsApp Flows: Setting up WhatsApp Flows
WhatsApp Welcome Message: Setting up Conversational Components
Webhook Configuration Documentation: WhatsApp Cloud API Webhook
Authentication Message Documentation: Create and Send Authentication Message
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 return an invalid parameter error (#100) (Credits @Tekkharibo)
Take note: Downloading media from the generated Whatsapp media URL will require one to specify the app name and version value to be set as useragent for the download media function to work. It is included as properties for the config class. (Credits @atmtrujillo)
Note: This package is WIP. The capabilities of Cloud API will be reflected soon. Feel free to contribute!
[x] Sending Messages
[x] Receiving Message (via Webhook)
[x] WhatsApp Business Management API
[x] Sample project
PM> Install-Package WhatsappBusiness.CloudApi
> dotnet add package WhatsappBusiness.CloudApi
Before you proceed kindly acquaint yourself with WhatsApp Business Cloud Apis by going through the Docs in Meta's developer portal if you like.
Obtain a Temporary access token for the meta developers portal.
Ensure your project is running on the minimum 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 instance 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
}
using WhatsappBusiness.CloudApi.Configurations;
using WhatsappBusiness.CloudApi.Extensions;
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);
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);
// You need to create your authentication template message
AuthenticationTemplateMessageRequest authenticationTemplateMessageRequest = new();
authenticationTemplateMessageRequest.To = sendTemplateMessageViewModel.RecipientPhoneNumber;
authenticationTemplateMessageRequest.Template = new();
authenticationTemplateMessageRequest.Template.Name = sendTemplateMessageViewModel.TemplateName;
authenticationTemplateMessageRequest.Template.Language = new();
authenticationTemplateMessageRequest.Template.Language.Code = LanguageCode.English_US;
authenticationTemplateMessageRequest.Template.Components = new List<AuthenticationMessageComponent>()
{
new AuthenticationMessageComponent()
{
Type = "body",
Parameters = new List<AuthenticationMessageParameter>()
{
new AuthenticationMessageParameter()
{
Type = "text",
Text = "J$FpnYnP" // One time password value
}
}
},
new AuthenticationMessageComponent()
{
Type = "button",
SubType = "url",
Index = 0,
Parameters = new List<AuthenticationMessageParameter>()
{
new AuthenticationMessageParameter()
{
Type = "text",
Text = "J$FpnYnP" // One time password value
}
}
}
};
var results = await _whatsAppBusinessClient.SendAuthenticationMessageTemplateAsync(authenticationTemplateMessageRequest);
FlowMessageRequest flowMessageRequest = new FlowMessageRequest();
flowMessageRequest.To = sendFlowMessageViewModel.RecipientPhoneNumber;
flowMessageRequest.Interactive = new FlowMessageInteractive();
flowMessageRequest.Interactive.Header = new FlowMessageHeader();
flowMessageRequest.Interactive.Header.Type = "text";
flowMessageRequest.Interactive.Header.Text = "Header flow";
flowMessageRequest.Interactive.Body = new FlowMessageBody();
flowMessageRequest.Interactive.Body.Text = "Body flow";
flowMessageRequest.Interactive.Footer = new FlowMessageFooter();
flowMessageRequest.Interactive.Footer.Text = "Footer flow";
flowMessageRequest.Interactive.Action = new FlowMessageAction();
flowMessageRequest.Interactive.Action.Parameters = new FlowMessageParameters();
flowMessageRequest.Interactive.Action.Parameters.FlowToken = sendFlowMessageViewModel.FlowToken;
flowMessageRequest.Interactive.Action.Parameters.FlowId = sendFlowMessageViewModel.FlowId;
flowMessageRequest.Interactive.Action.Parameters.FlowCta = sendFlowMessageViewModel.FlowButtonText;
flowMessageRequest.Interactive.Action.Parameters.FlowAction = sendFlowMessageViewModel.SelectedFlowAction;
flowMessageRequest.Interactive.Action.Parameters.IsInDraftMode = (sendFlowMessageViewModel.SelectedMode.Equals("Draft", StringComparison.OrdinalIgnoreCase));
flowMessageRequest.Interactive.Action.Parameters.FlowActionPayload = new FlowActionPayload();
flowMessageRequest.Interactive.Action.Parameters.FlowActionPayload.Screen = sendFlowMessageViewModel.ScreenId;
var results = await _whatsAppBusinessClient.SendFlowMessageAsync(flowMessageRequest);
FlowTemplateMessageRequest flowTemplateMessageRequest = new FlowTemplateMessageRequest();
flowTemplateMessageRequest.To = sendTemplateMessageViewModel.RecipientPhoneNumber;
flowTemplateMessageRequest.Template = new();
flowTemplateMessageRequest.Template.Name = sendTemplateMessageViewModel.TemplateName;
flowTemplateMessageRequest.Template.Language = new();
flowTemplateMessageRequest.Template.Language.Code = LanguageCode.English_US;
flowTemplateMessageRequest.Template.Components = new List<FlowMessageComponent>()
{
new FlowMessageComponent()
{
Type = "button",
SubType = "flow",
Index = 0,
Parameters = new List<FlowTemplateMessageParameter>()
{
new FlowTemplateMessageParameter()
{
Type = "action",
Action = new FlowTemplateMessageAction()
{
FlowToken = "",
}
}
}
}
};
var results = await _whatsAppBusinessClient.SendFlowMessageTemplateAsync(flowTemplateMessageRequest);
First, you need to setup the callback url and verify the 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();
}
app.Use((context, next) =>
{
context.Request.EnableBuffering();
return next();
});
// Validation implementation
string stringifiedBody;
string xHubSignature256 = context.Request.Headers["X-Hub-Signature-256"].ToString();
context.Request.Body.Seek(0, SeekOrigin.Begin);
using (var sr = new StreamReader(context.Request.Body))
{
stringifiedBody = await sr.ReadToEndAsync().ConfigureAwait(false);
}
string xHubSignature256Result = FacebookWebhookHelper.CalculateSignature(_config.GetValue<string("Facebook:AppSecret"), stringifiedBody);
if (!String.Equals(xHubSignature256, xHubSignature256Result,StringComparison.InvariantCultureIgnoreCase))
{
return Results.Unauthorized();
}
WhatsAppBusinessClient Throws WhatsappBusinessCloudAPIException
It is your role as the developer to catch
the exception and continue processing in your application. The snippet below shows how you can catch the WhatsappBusinessCloudAPIException.
using WhatsappBusiness.CloudApi.Exceptions; // add this to your class or namespace
try
{
return await _whatsAppBusinessClient.SendTextMessageAsync(textMessageRequest);
}
catch (WhatsappBusinessCloudAPIException ex)
{
_logger.LogError(ex, ex.Message);
}
If you will face any issues with the usage of this package please raise one so that we can quickly fix it as soon as possible.
This is an open-source project under MIT License
so anyone is welcome to contribute from typos, to source code to documentation.