Neura.CRM.SDK 1.2.0
Neura.CRM.SDK
A typed .NET client for the NeuraCore CRM WhatsApp API, plus the models for the callbacks the
platform POSTs back to you. netstandard2.0, so it drops into .NET Framework and .NET alike.
The live documentation for the same contract is served by the API itself:
| Page | What it is |
|---|---|
/api/developers |
The quickstart: credentials, token, first send, callbacks. |
/api/swagger |
Every endpoint with its request and response shapes, and a console. |
/api/callbacks |
Every callback, an example body per message type, the retry rules, and a throwaway inbox you can fire a real delivery at. |
Sending
Register the client, authenticate once, send:
services.AddNeuraWhatsappClient("https://your-deployment.example.com/");
public class QuoteNotifier
{
private readonly INeuraWhatsappClient _whatsapp;
private static readonly AuthenticationModel Credentials = new AuthenticationModel
{
ApiKey = "your-api-key", // issued per company
ApiSecret = "00000000-0000-..." // the number you send from
};
public QuoteNotifier(INeuraWhatsappClient whatsapp) => _whatsapp = whatsapp;
public async Task<Guid?> SendAsync(string number)
{
// Authenticates only when the cached token is missing or nearly expired: tokens last
// 7 days and Authenticate is limited to 5 attempts a minute.
await _whatsapp.EnsureAuthenticatedAsync(Credentials);
var result = await _whatsapp.SendMessageAsync(new WhatsAppMessage
{
To = number, // digits only, country code, no plus
Message = "Your quote is ready.",
Reference = "ORDER-1042" // your own reference, not shown to the contact
});
// A send the API understood but could not do comes back 200 with Success false -
// check it as well as catching NeuraApiException.
if (!result.Success)
throw new InvalidOperationException(result.Error?.ErrorMessage);
return result.MessageID; // every callback about this message quotes it
}
}
Anything that is not 2xx throws NeuraApiException (a HttpRequestException), which carries the
status and body - IsUnauthorized means re-authenticate, IsRateLimited means back off.
Beyond plain text there is media, buttons, lists, flows, templates, authentication codes, carousels,
URL buttons and generated documents; plus templates, automations and the business profile. One method
each on INeuraWhatsappClient.
Receiving
Set the CallbackUrl on your number to one base URL and the platform POSTs each callback to that URL
plus its own endpoint. Implement INeuraCallbackHandler (or derive from NeuraCallbackHandler and
override only what you need) and let CallbackDispatcher route the bodies:
public class Receiver : NeuraCallbackHandler
{
public override Task MessageReceivedAsync(MessageModel m, CancellationToken ct = default)
{
if (m.MessageType == MessageTypes.Interactive)
{
var reply = CallbackPayload.ParseInteractiveReply(m.Content);
// Match on the id you set, never on the title.
if (reply?.ID == "quote_yes") { /* ... */ }
}
return Task.CompletedTask;
}
public override Task StatusReceivedAsync(WhatsappStatus s, CancellationToken ct = default)
{
// Statuses arrive out of order - keep the highest rank you have seen, not the last one.
if (MessageStatuses.Rank(s.MessageStatus) > RankStoredFor(s.MessageID))
Store(s);
return Task.CompletedTask;
}
}
services.AddNeuraCallbackHandler<Receiver>(); // registers the handler and the dispatcher
// ASP.NET Core: one route for every callback.
app.MapPost("/callbacks/{endpoint}", async (HttpRequest request, CallbackDispatcher dispatcher) =>
{
using var reader = new StreamReader(request.Body);
var result = await dispatcher.DispatchAsync(request.Path, await reader.ReadToEndAsync());
return Results.StatusCode(result.SuggestedStatusCode);
});
The endpoints, and the body each one sends:
| Endpoint | Body |
|---|---|
/WhatsappReceived |
MessageModel (IsFromMe false) |
/WhatsappSent |
MessageModel (IsFromMe true) |
/StatusReceived |
WhatsappStatus |
/AutomationStep |
AutomationStepCallback |
/AutomationComplete |
AutomationCompleteCallback |
/UserPreferenceReceived |
UserPreferenceCallback |
CallbackEndpoints, MessageTypes, MessageStatuses, InteractiveTypes, AutomationStatuses and
PreferenceValues hold the vocabularies as constants, and CallbackDelivery holds the delivery
rules (nine attempts over about 28 minutes; 404 and 405 are terminal).
Reading Content and Data
Content and Data change shape completely with MessageType, and Data arrives as a JSON string
rather than an object. CallbackPayload reads them, returning null instead of throwing when a
payload is not the shape asked for:
var interactive = CallbackPayload.ParseInteractiveData(m.Data); // what was sent, or what was tapped
var media = CallbackPayload.ParseMedia(m.Data); // original file name, mime type, thumbnail
var location = CallbackPayload.ParseLocation(m.Content); // latitude/longitude
var reply = CallbackPayload.ParseInteractiveReply(m.Content); // button/list reply id and title
var form = CallbackPayload.ParseFlowResponse(m.Content); // a submitted WhatsApp Flow
Three rules worth building in from the start
- Answer 2xx first, work afterwards. A receiver that holds the request open past the delivery timeout is retried even though it was working, which reaches you as duplicates.
- De-duplicate. On
IDfor the message callbacks, onMessageID+MessageStatusfor statuses. - Nothing is ordered. Inbound, outbound and statuses are delivered independently, and a retry reorders whatever it lands behind.
Proving a callback came from us
There is no signature header. Configure CallbackAuth against your number and check it with
CallbackAuthValidator - basic, a custom header, or a query parameter. Do not check the source IP;
it is not stable.
var auth = new CallbackAuthValidator(CallbackAuthModes.Header, "X-Api-Key", secret);
if (!auth.IsValid(name => request.Headers[name]))
return Results.Unauthorized();
No packages depend on Neura.CRM.SDK.
.NET Standard 2.0
- Microsoft.Extensions.Http (>= 10.0.11)
- System.Text.Json (>= 10.0.11)