Lib.WhatsAppMeta 1.0.89

dotnet add package Lib.WhatsAppMeta --version 1.0.89
                    
NuGet\Install-Package Lib.WhatsAppMeta -Version 1.0.89
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Lib.WhatsAppMeta" Version="1.0.89" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Lib.WhatsAppMeta" Version="1.0.89" />
                    
Directory.Packages.props
<PackageReference Include="Lib.WhatsAppMeta" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Lib.WhatsAppMeta --version 1.0.89
                    
#r "nuget: Lib.WhatsAppMeta, 1.0.89"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Lib.WhatsAppMeta@1.0.89
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Lib.WhatsAppMeta&version=1.0.89
                    
Install as a Cake Addin
#tool nuget:?package=Lib.WhatsAppMeta&version=1.0.89
                    
Install as a Cake Tool

Lib.WhatsAppMeta

A .NET library for integrating with the WhatsApp Business API (Meta). It provides a ready-to-use service for sending template messages, creating templates (including with media), uploading media, webhook verification and processing, and persisting WhatsApp-related data via Entity Framework Core. Designed to work both in the same solution and when installed as a package in another project.


Table of contents


Features

  • WhatsApp Meta API – Send template messages, create templates, upload media, manage business account
  • Entity Framework Core – DbContext and entities for credentials, templates, message list, webhook message status
  • Custom schema – Tables can use a SQL schema (e.g. crm.MessageTemplates)
  • CRM bulk sending – SendTemplateMessageCrm with template lookup, image upload, and send-list persistence
  • Webhook – Verify Meta webhook (GET) and process incoming messages, status updates, and history (POST). Optional default processor persists to the same DbContext; you can replace it with your own. Register AddWhatsAppMediaDownloader() (and optionally IWhatsAppBaseUrlProvider) so incoming media gets a public URL instead of blank when using the package in another project.
  • Audit fields (optional) – IWhatsAppMetaUserIdProvider for CreatedBy/UpdatedBy/DeletedBy; works without it in another project (inserts still succeed)
  • Dependency injection – Designed for ASP.NET Core / any host with DI; works when used as a NuGet package in another solution

Installation

NuGet

dotnet add package Lib.WhatsAppMeta

Project reference (same solution)

<ProjectReference Include="path\to\Lib.WhatsAppMeta.csproj" />

Package version: 1.0.58 (multi-targets net7.0 and net10.0).


Package changes (v1.0.58)

Area Before Now (1.0.58)
Target frameworks .NET 7 only .NET 7 and .NET 10
EF Core 7.0.11 7.0.11 (net7) / 10.0.8 (net10)
App dependencies App.Infrastructure, Lib.Core, Lib.Config None — standalone NuGet
CRM provider classes WhatsAppCrmClientIdProvider, etc. in the lib Removed — implement interfaces in your app
SignalR adapter WhatsAppSignalRNotifierAdapter in the lib Removed — implement IWhatsAppRealtimeNotifier
Stored procedures FromSqlRaw ADO.NET (works on EF 7 and EF 10)
Inbox list DTOs App.Domain view models ChatListEntryDto, BroadcastListEntryDto, ContactListEntryDto

Common errors after upgrade

Error Fix
WhatsAppCrmClientIdProvider could not be found Implement IWhatsAppCrmClientIdProvider in your app — see Scenario B
WhatsAppSignalRNotifierAdapter could not be found Implement IWhatsAppRealtimeNotifier in your app
SetPropertyCalls type load error Use 1.0.58 + matching EF Core major version (10.0.x on .NET 10)

.NET 7 host

<PackageReference Include="Lib.WhatsAppMeta" Version="1.0.58" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.11" />

.NET 10 host

<PackageReference Include="Lib.WhatsAppMeta" Version="1.0.58" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.8" />

Quick start

  1. Register services (e.g. in Program.cs or your DI configuration):
using Lib.WhatsAppMeta.Extensions;

// Uses connection string named "db" from IConfiguration
builder.Services.AddWhatsAppMeta(builder.Configuration);

// Optional: webhook verify token (required if you expose a webhook endpoint)
builder.Services.AddWhatsAppMetaWebhook("YourVerifyToken");

// Optional: install DB objects (tables, schema) on startup
builder.Services.UseWhatsAppMetaMigrations();
  1. Inject and use the service (always use the interface):
using Lib.WhatsAppMeta.Services;

public class MyHandler
{
    private readonly IWhatsappMetaService _whatsappMetaService;

    public MyHandler(IWhatsappMetaService whatsappMetaService)
    {
        _whatsappMetaService = whatsappMetaService;
    }

    public async Task DoSomething()
    {
        var response = await _whatsappMetaService.SendTemplateMessage(
            jsonPayload, accessToken, CancellationToken.None);
    }
}

Service registration

AddWhatsAppMeta (extension methods)

Overload Use when
AddWhatsAppMeta(IConfiguration, connectionStringName?, schema?) You want to read the connection string from configuration (e.g. ConnectionStrings:db).
AddWhatsAppMeta(string connectionString, schema?) You have the connection string in code or from another source.

Parameters

  • connectionStringName – Key in IConfiguration (default: "db") → configuration.GetConnectionString("db").
  • schema – SQL schema for tables (e.g. "crm" → crm.MessageTemplates). Optional; if omitted, default schema is used.
  • connectionString – Full SQL Server connection string (second overload).

Examples

using Lib.WhatsAppMeta.Extensions;

// Default: connection string "db", default schema
builder.Services.AddWhatsAppMeta(builder.Configuration);

// Custom connection string key and schema "crm"
builder.Services.AddWhatsAppMeta(builder.Configuration, "db", "crm");

// Direct connection string and schema
builder.Services.AddWhatsAppMeta(
    "Server=.;Database=MyDb;Integrated Security=true;TrustServerCertificate=true",
    "crm");

What gets registered

  • IWhatsappMetaService → WhatsappMetaService (scoped)
  • WhatsAppMetaDbContext (scoped), when a non-empty connection string is provided
  • IWhatsAppWebhookService → WhatsAppWebhookService (scoped)
  • IWhatsAppWebhookProcessor → DefaultWhatsAppWebhookProcessor (scoped), unless you register your own (your registration wins)
  • IWhatsAppMetaUserIdProvider → HttpContextUserIdProviderAdapter (scoped, optional bridge for audit fields)

AddWhatsAppMetaWebhook (verify token)

Call after AddWhatsAppMeta when you expose a webhook URL for Meta to call:

Parameter Description
verifyToken Token Meta sends as hub.verify_token on GET; set per project (e.g. "WAVerifytokenJMS").
builder.Services.AddWhatsAppMetaWebhook("YourVerifyToken");

Configuration

appsettings.json

Connection string (required for DB and migrations):

{
  "ConnectionStrings": {
    "db": "Server=localhost;Database=YourDb;User Id=sa;Password=YourPassword;TrustServerCertificate=True;"
  }
}

Facebook / WhatsApp (needed for CRM and template creation):

{
  "Facebook": {
    "ClientId": "your-meta-app-client-id"
  },
  "WhatsAppMeta": {
    "WebhookVerifyToken": "WAVerifytokenJMS"
  },
  "BaseUrl": "https://yoursite.com/"
}

Bind Facebook to your own settings class and use it in your IWhatsAppCrmClientIdProvider implementation (see Scenario B).


Using the service

  • Always depend on the interface IWhatsappMetaService. Do not inject the concrete type WhatsappMetaService in application code.
// ✅ Correct
public MyService(IWhatsappMetaService whatsappMetaService) { }

// ❌ Wrong – can break DI in some setups
public MyService(WhatsappMetaService whatsappMetaService) { }

API reference

All methods are on IWhatsappMetaService (implemented by WhatsappMetaService).

Method Description
SendTemplateMessage(string json, string accessToken, CancellationToken) Sends a template message using raw JSON payload.
SendTemplateMessageCrm(SendTemplateMessageCrmRequest, SendTemplateMessageCrmCredential, IWhatsAppCrmSendContext, CancellationToken) Sends template messages in a CRM flow: template lookup, optional image upload, body/header params, location, and send-list persistence. Requires CRM context and providers to be registered.
ProcessMediaUploadAsync(string mediaInput, string clientId, string phoneNumberId, string accessToken) Uploads media (e.g. base64 or URL) and returns a media ID.
CreateMessageTemplate(CreateMessageTemplateRequest, string wabaId, string accessToken, CancellationToken) Creates a message template (no media upload).
CreateMessageTemplateWithMedia(object commandRequest, string wabaId, string accessToken, string clientId, string phoneNumberId, ICreateTemplateContext?, MessageTemplateSaveOptions?, CancellationToken) Validates base64 size, uploads media, creates template, and optionally persists it via ICreateTemplateContext.
FetchWhatsappBusinessAccount(string wabaId, string accessToken, CancellationToken) Fetches WhatsApp Business Account details.
GetTemplateById / GetTemplateByIdAsString Gets a template by ID.
GetAllTemplates(string wabaId, string accessToken, string? fields, int? limit, CancellationToken) Lists templates.
RegisterPhoneNumber(string phoneNumberId, string pin, string codeMethod, string accessToken) Registers a phone number.
SubscribeAppToWebhook(string wabaId, string accessToken) Subscribes app to webhook.
SendMessageFromInbox(object commandRequest, string phoneNumberId, string accessToken, CancellationToken) Sends a message from inbox. Use SendMessageFromInboxRequest (in Lib.WhatsAppMeta.Models) for a shared request shape across projects.

Request/response types live in Lib.WhatsAppMeta.Models and related namespaces (e.g. SendTemplateMessageCrmRequest, SendTemplateMessageCrmCredential, CreateMessageTemplateRequest, WhatsAppResponseModel, CreateMessageTemplateResult).


Webhook (Meta callback)

Meta sends GET requests to verify your webhook URL and POST requests with incoming messages, status updates, and history. The Lib handles verification and payload parsing; you expose a thin controller and optionally replace the default processor.

1. Set the verify token

builder.Services.AddWhatsAppMetaWebhook("YourVerifyToken");  // e.g. "WAVerifytokenJMS"

Use the same value in the Meta App Dashboard under Webhook Verify token.

2. Add a webhook controller

Inject IWhatsAppWebhookService. For GET, call VerifyWebhook and return the challenge when valid. For POST, read the raw body and call ProcessWebhookPayloadAsync; the Lib parses the payload and invokes your processor (or the default). Return 200 so Meta does not retry.

using Lib.WhatsAppMeta.Webhook;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/[controller]")]
public class WhatsappWebhookController : ControllerBase
{
    private readonly IWhatsAppWebhookService _webhookService;

    public WhatsappWebhookController(IWhatsAppWebhookService webhookService)
    {
        _webhookService = webhookService;
    }

    [HttpGet]
    public IActionResult VerifyWebhook([FromQuery] string hub_mode, [FromQuery] string hub_verify_token, [FromQuery] string hub_challenge)
    {
        var result = _webhookService.VerifyWebhook(hub_mode, hub_verify_token, hub_challenge);
        if (result.IsValid)
            return Ok(result.Challenge);
        return Forbid();
    }

    [HttpPost]
    public async Task<IActionResult> ProcessWebhook(CancellationToken cancellationToken)
    {
        using var reader = new StreamReader(Request.Body);
        var body = await reader.ReadToEndAsync(cancellationToken);
        await _webhookService.ProcessWebhookPayloadAsync(body, cancellationToken);
        return Ok();
    }
}

3. Default processor vs custom processor

  • Default processor – The Lib registers DefaultWhatsAppWebhookProcessor, which persists incoming messages, status updates, and history to WhatsAppMetaDbContext (same DB as WhatsappMetaService). No extra code needed; insert/update works when using the Lib in another project.
  • Custom processor – To use your own DB, SignalR, or external APIs, implement IWhatsAppWebhookProcessor and register it (your registration overrides the default):
public interface IWhatsAppWebhookProcessor
{
    Task ProcessIncomingMessagesAsync(WebhookPayloadContext context, IReadOnlyList<IncomingMessageDto> messages, CancellationToken cancellationToken = default);
    Task ProcessStatusUpdatesAsync(WebhookPayloadContext context, IReadOnlyList<StatusUpdateDto> statuses, CancellationToken cancellationToken = default);
    Task ProcessHistoryAsync(WebhookPayloadContext context, IReadOnlyList<HistoryMessageDto> history, CancellationToken cancellationToken = default);
}

// In Startup/Program:
builder.Services.AddScoped<IWhatsAppWebhookProcessor, MyWebhookProcessor>();

DTOs and context are in Lib.WhatsAppMeta.Webhook (WebhookPayloadContext, IncomingMessageDto, StatusUpdateDto, HistoryMessageDto). The Lib does not rethrow from ProcessWebhookPayloadAsync so you can always return 200 and avoid Meta retries.

4. Realtime / SignalR (optional)

When using the default webhook processor, you can push incoming messages and status updates to real-time clients (e.g. SignalR) by implementing IWhatsAppRealtimeNotifier and registering it. The default processor then calls your notifier after persisting each incoming message and each status update (sent / delivered / read / failed). Status notifications set MessageStatus on WhatsAppRealtimeNotification.

  1. Implement IWhatsAppRealtimeNotifier (e.g. an adapter that calls your IHubContext<ChatHub> or IRealTimeService):
public class MySignalRNotifier : IWhatsAppRealtimeNotifier
{
    private readonly IRealTimeService _realTimeService; // or IHubContext<ChatHub>
    public MySignalRNotifier(IRealTimeService realTimeService) => _realTimeService = realTimeService;

    public async Task NotifyAsync(WhatsAppRealtimeNotification n, CancellationToken ct)
    {
        await _realTimeService.SendMessageAsync(n.MessageId, n.ContextMessageId, n.EventName, n.ChatStatus,
            n.FromPhoneNumber, n.ToRegisteredUserId, n.Message, n.ContactInfo ?? "", n.TotalRecords, n.ErrorMessage);
    }
}
  1. Register your notifier before AddWhatsAppMeta (so it is used instead of the default no-op):
builder.Services.AddWhatsAppRealtimeNotifier<MySignalRNotifier>();
builder.Services.AddWhatsAppMeta(builder.Configuration);
// Use the default processor (do not register a custom IWhatsAppWebhookProcessor) so the lib pushes via your notifier.

Event names and payload match the app’s ChatHub: ReceiveMessage for incoming messages, SendMessage for status updates. Use WhatsAppRealtimeStatus.Received (1) and WhatsAppRealtimeStatus.Send (2), or your app’s enum if values match.


CRM template sending (SendTemplateMessageCrm)

SendTemplateMessageCrm sends template messages using template metadata, optional media upload, and persist send-list entries. It needs:

  1. IWhatsAppCrmSendContext – Implemented by the Lib’s WhatsAppCrmSendContext, which in turn needs the four providers below.
  2. Four provider interfaces – Implement in your app (NuGet) or use JewelSteps App.Infrastructure implementations (monorepo only). See Scenario A / B below.

Provider interfaces (Lib.WhatsAppMeta.Models)

Interface Purpose
IWhatsAppCrmClientIdProvider Returns WhatsApp/Meta client ID (e.g. from config).
IWhatsAppImageUpload Uploads an image (e.g. base64) and returns UploadImageResultDto (e.g. path or URL).
IBaseUrlProvider Returns your application’s base URL.
IWhatsAppErrorParser Parses API error JSON and returns a user-facing message.

WhatsAppCrmSendContext (Lib) uses these four plus WhatsAppMetaDbContext for template lookup and send-list persistence.


Scenario A: JewelStepsAdmin monorepo (project reference + App.Infrastructure)

In JewelStepsAdmin only, CRM provider classes live in App.Infrastructure/Services/FaceBook/WhatsAppCrmProviders.cs (not in the NuGet package). Register them from App.Application/ApplicationServices.cs:

using App.Infrastructure.Services.FaceBook;
using Lib.WhatsAppMeta.Extensions;
using Lib.WhatsAppMeta.Models;
using Lib.WhatsAppMeta.Services;

builder.Services.Configure<FacebookSettings>(builder.Configuration.GetSection("FacebookSettings"));
builder.Services.AddWhatsAppMeta(builder.Configuration, "db");
builder.Services.AddScoped<IWhatsAppCrmClientIdProvider, WhatsAppCrmClientIdProvider>();
builder.Services.AddScoped<IWhatsAppImageUpload, WhatsAppCrmImageUpload>();
builder.Services.AddScoped<IBaseUrlProvider, WhatsAppCrmBaseUrlProvider>();
builder.Services.AddScoped<IWhatsAppErrorParser, WhatsAppCrmErrorParser>();
builder.Services.AddScoped<IWhatsAppCrmSendContext, WhatsAppCrmSendContext>();

Scenario B: Using the Lib as a NuGet package (other solution)

The package does not ship WhatsAppCrmClientIdProvider, WhatsAppSignalRNotifierAdapter, or other app-specific classes. Implement the interfaces in your project and register them.

1. Provider implementations (folder e.g. WhatsApp/Providers/):

using Lib.WhatsAppMeta.Models;
using Microsoft.Extensions.Options;

public class FacebookSettings { public string? ClientId { get; set; } }

public class AppWhatsAppCrmClientIdProvider : IWhatsAppCrmClientIdProvider
{
    private readonly FacebookSettings _settings;
    public AppWhatsAppCrmClientIdProvider(IOptions<FacebookSettings> opts) =>
        _settings = opts?.Value ?? throw new ArgumentNullException(nameof(opts));
    public string ClientId => _settings.ClientId ?? "";
}

public class AppWhatsAppImageUpload : IWhatsAppImageUpload
{
    public Task<UploadImageResultDto> UploadImageAsync(string base64Path, string folder, CancellationToken ct = default)
        => Task.FromResult(new UploadImageResultDto { ImagePath = null }); // use your upload service
}

public class AppBaseUrlProvider : IBaseUrlProvider
{
    private readonly IHttpContextAccessor _http; private readonly IConfiguration _config;
    public AppBaseUrlProvider(IHttpContextAccessor http, IConfiguration config) { _http = http; _config = config; }
    public string GetBaseUrl() => _http.HttpContext?.Request != null
        ? $"https://{_http.HttpContext.Request.Host}/"
        : (_config["BaseUrl"] ?? "https://yoursite.com/");
}

public class AppWhatsAppErrorParser : IWhatsAppErrorParser
{
    public string ParseErrorMessage(string apiError) { /* parse Graph API error JSON */ return apiError; }
}

2. SignalR (optional) — implement IWhatsAppRealtimeNotifier in your app and register with AddWhatsAppRealtimeNotifier<YourAdapter>().

3. Central DI registration (recommended pattern):

using Lib.WhatsAppMeta.Extensions;
using Lib.WhatsAppMeta.Models;
using Lib.WhatsAppMeta.Services;

public static class WhatsAppMetaConfiguration
{
    public static IServiceCollection AddWhatsAppMetaServices(this IServiceCollection services, IConfiguration configuration)
    {
        services.Configure<FacebookSettings>(configuration.GetSection("Facebook"));
        services.AddWhatsAppMediaDownloader();
        services.AddWhatsAppRealtimeNotifier<AppWhatsAppSignalRNotifierAdapter>(); // your adapter
        services.AddWhatsAppMeta(configuration, "db", "crm");

        var verifyToken = configuration["WhatsAppMeta:WebhookVerifyToken"] ?? "WAVerifytokenJMS";
        services.AddWhatsAppMetaWebhook(verifyToken);

        services.AddScoped<IWhatsAppCrmClientIdProvider, AppWhatsAppCrmClientIdProvider>();
        services.AddScoped<IWhatsAppImageUpload, AppWhatsAppImageUpload>();
        services.AddScoped<IBaseUrlProvider, AppBaseUrlProvider>();
        services.AddScoped<IWhatsAppErrorParser, AppWhatsAppErrorParser>();
        services.AddScoped<IWhatsAppCrmSendContext, WhatsAppCrmSendContext>(); // from package
        services.AddScoped<ICreateTemplateContext, AppCreateTemplateContext>(); // your impl

        if (configuration.GetConnectionString("db") is not null)
        {
            services.UseWhatsAppMetaMigrations();
            services.UseWhatsAppMetaProgrammability(); // stored procedures + indexes
        }
        return services;
    }
}

Program.cs

builder.Services.AddHttpContextAccessor();
builder.Services.AddWhatsAppMetaServices(builder.Configuration);

Migration checklist (from older package)

  • Upgrade to 1.0.58
  • Align EF Core version (7.x or 10.x) with target framework
  • Replace WhatsAppCrmClientIdProvider with your AppWhatsAppCrmClientIdProvider
  • Replace WhatsAppSignalRNotifierAdapter with your IWhatsAppRealtimeNotifier impl
  • Keep services.AddScoped<IWhatsAppCrmSendContext, WhatsAppCrmSendContext>()
  • Deploy SPs: UseWhatsAppMetaProgrammability() or extract SQL (see Stored procedures)
  • Test: template send, inbox, webhook, mark-as-read

Creating message templates

  • Without media: use CreateMessageTemplate(CreateMessageTemplateRequest, wabaId, accessToken, cancellationToken).
  • With media (image/video/document): use CreateMessageTemplateWithMedia(object commandRequest, wabaId, accessToken, clientId, phoneNumberId, ICreateTemplateContext?, MessageTemplateSaveOptions?, cancellationToken). The Lib validates base64 size, uploads media, then creates the template. Optionally pass ICreateTemplateContext and MessageTemplateSaveOptions to persist the template in your DB.

Optional persistence after create

Implement ICreateTemplateContext and pass it (and MessageTemplateSaveOptions) when calling CreateMessageTemplateWithMedia:

public interface ICreateTemplateContext
{
    Task SaveMessageTemplateAsync(MessageTemplateSaveDto dto, CancellationToken cancellationToken = default);
}

Register your implementation and pass it from your app layer when invoking CreateMessageTemplateWithMedia.


Entities and DbContext

DbContext: WhatsAppMetaDbContext (from Lib.WhatsAppMeta.Data).

Entities (Lib.WhatsAppMeta.Entities):

  • WAUserCredential – WhatsApp user credentials
  • WAUserCredentialUsersMapping
  • WAWebhookMsgStatus – Webhook message/status/history (default processor persists here)
  • SendWAMessageList – Sent message list (used by CRM send and status updates)
  • MetaChargeList
  • MessageTemplate – Template metadata (used by CRM send)
  • BroadcastCampaign – Broadcast campaigns
  • BroadcastRecipient – Broadcast recipients

You can inject WhatsAppMetaDbContext for direct DB access when needed.


Data insert and webhook in any project

When you use the Lib as a package in another project, the same DbContext and patterns apply so inserts and updates work without app-specific code.

Audit fields (CreatedBy, UpdatedBy, DeletedBy)
WhatsAppMetaDbContext.SaveChangesAsync sets these when an IWhatsAppMetaUserIdProvider is registered. If you do not register one, the fields remain null and inserts still work. To set them:

builder.Services.AddScoped<IWhatsAppMetaUserIdProvider, MyUserIdProvider>();

Webhook persistence
See Webhook (Meta callback). The default processor persists to WhatsAppMetaDbContext; register your own IWhatsAppWebhookProcessor to override (e.g. your DB or SignalR).


Database setup and migrations

Automatic (recommended)
Call after AddWhatsAppMeta:

builder.Services.UseWhatsAppMetaMigrations();

This runs the Lib’s embedded SQL installation (tables, schema) at startup. Ensure the connection string is valid and the app has DB permissions.

Manual
If you use EF Core migrations in the Lib project:

dotnet ef migrations add InitialCreate --context WhatsAppMetaDbContext --project Lib.WhatsAppMeta
dotnet ef database update --context WhatsAppMetaDbContext --project Lib.WhatsAppMeta

Stored procedures and inbox indexes (refresh SQL / NuGet package)

The Lib ships six stored procedures plus InboxPerformanceIndexes.sql (see table in package under content/Lib.WhatsAppMeta.Sql/): sp_GetAllChatByNumber, sp_GetAllContactList, sp_GetAllBroadcastList, sp_GetConversationStats, sp_GetDistinctUnreadRecipientCount, sp_MarkRecipientMessagesAsRead, and inbox indexes.
Your host project’s “refresh SQL” (or programmability step) usually reads from its own output folder (e.g. Programmability/StoredProcedures/*.sql). When you use Lib.WhatsAppMeta as a package, those scripts are not in your project by default.

Extract the SQL files to your Programmability/StoredProcedures/ folder so your existing refresh SQL step picks them up:

using Lib.WhatsAppMeta.Data;

// One-time setup (e.g. in a migration script or startup):
var targetDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Programmability", "StoredProcedures");
WhatsAppMetaStoredProcedures.ExtractStoredProceduresToDirectory(targetDir);

After extraction, your refresh SQL step will find them in Programmability/StoredProcedures/ and include them automatically.

Deploy all stored procedures and inbox indexes in one call:

using Lib.WhatsAppMeta.Data;
using Lib.WhatsAppMeta.Extensions;

// Startup (after AddWhatsAppMeta):
builder.Services.UseWhatsAppMetaProgrammability();

// Or from your own DbContext / refresh handler:
WhatsAppMetaStoredProcedures.ApplyProgrammability(yourDbContext, schema: "dbo");

Stored procedures only (no indexes):

WhatsAppMetaStoredProcedures.ExecuteStoredProcedures(yourDbContext);
WhatsAppMetaObjectsInstaller.InstallInboxPerformanceIndexes(yourDbContext.Database.GetDbConnection(), "dbo");

Option 3: Get scripts and handle yourself

Enumerate scripts and write/execute them as needed:

foreach (var (fileName, content) in WhatsAppMetaStoredProcedures.GetStoredProcedureScripts())
{
    // Write to disk, execute, etc.
}

Note: SQL files are included as ContentFiles in the NuGet package (content/Lib.WhatsAppMeta.Sql/StoredProcedures/). Use Option 1 to extract them into your project, or Option 2 for programmatic deploy.

SQL quick reference

Action Code
Deploy all SPs + indexes services.UseWhatsAppMetaProgrammability()
Deploy from code WhatsAppMetaStoredProcedures.ApplyProgrammability(dbContext, "crm")
Extract to disk WhatsAppMetaStoredProcedures.ExtractStoredProceduresToDirectory(targetDir)

Requirements

  • .NET 7.0 or .NET 10.0 (package multi-targets both)
  • Entity Framework Core matching your target framework:
    • .NET 7 host → Microsoft.EntityFrameworkCore.SqlServer 7.0.x
    • .NET 10 host → Microsoft.EntityFrameworkCore.SqlServer 10.0.x
  • SQL Server (for DbContext and CRM persistence)
  • WhatsApp Business API (Meta) credentials and configuration

Troubleshooting

Error or symptom What to do
Unable to resolve service for type 'WhatsappMetaService' Inject IWhatsappMetaService, not the concrete WhatsappMetaService type.
WhatsAppCrmClientIdProvider could not be found Not in the NuGet package (v1.0.58+). Implement IWhatsAppCrmClientIdProvider in your app — see Scenario B.
WhatsAppSignalRNotifierAdapter could not be found Not in the package. Implement IWhatsAppRealtimeNotifier in your app.
Could not load type 'App.Infrastructure...' Do not use JewelSteps-only classes. Use Scenario B.
'WhatsAppImageUpload' could not be found Implement IWhatsAppImageUpload in your app (e.g. AppWhatsAppImageUpload).
Package downgrade (Microsoft.Extensions.DependencyInjection.Abstractions 8.0.0 → 7.0.0) The Lib references 8.0.0. If your app or another package forces 7.x, align versions (e.g. upgrade the app to 8.0.0 or use a single shared version).
Could not load type 'SetPropertyCalls`1' from Microsoft.EntityFrameworkCore.Relational EF Core version mismatch between the host app and an older Lib.WhatsAppMeta package that used FromSqlRaw. Upgrade to the latest Lib.WhatsAppMeta (stored procedures now use ADO.NET). Ensure all Microsoft.EntityFrameworkCore.* packages in the host use the same major version (e.g. all 10.0.x on .NET 10).
DbContext not registered / migrations not running Ensure you pass a non-null, non-empty connection string to AddWhatsAppMeta (via config key or direct string). Call UseWhatsAppMetaMigrations() only when the connection string is available.
Webhook GET returns 403 / verification fails Ensure AddWhatsAppMetaWebhook("YourToken") uses the same token as in the Meta App Dashboard (Webhook → Verify token).
Webhook POST succeeds but no data in DB Ensure WAUserCredentials has a row for the webhook's WABA ID and the credential is not soft-deleted. Default processor looks up by context.WabaId. If you use a custom processor, ensure it is registered (e.g. AddScoped<IWhatsAppWebhookProcessor, YourProcessor>()).
Method not found: GetCurrentUserId The Lib uses IWhatsAppMetaUserIdProvider (optional). If you see this from another assembly, do not inject the Lib's HttpContextUserIdProviderAdapter in that project, or implement your own IWhatsAppMetaUserIdProvider without depending on the missing method.

License

MIT. For issues or contributions, see the GitHub repository.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.89 0 11/13/2026
1.0.88 0 11/13/2026
1.0.87 131 9/30/2026
1.0.86 486 8/27/2026
1.0.85 137 8/27/2026
1.0.84 125 8/27/2026
1.0.83 128 8/26/2026
1.0.82 142 8/26/2026
1.0.81 125 8/26/2026
1.0.80 129 8/26/2026
1.0.79 128 8/26/2026
1.0.78 147 8/24/2026
1.0.77 159 8/20/2026
1.0.76 129 8/20/2026
1.0.75 180 8/13/2026
1.0.74 131 8/11/2026
1.0.73 127 8/11/2026
1.0.72 124 8/11/2026
1.0.71 195 7/31/2026
1.0.70 135 7/31/2026
Loading failed