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 ID for the message callbacks, on MessageID + MessageStatus for 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

Version Downloads Last updated
1.2.0 8 08/26/2026
1.1.0 19 05/26/2026
1.0.9 9 05/26/2026
1.0.8 15 05/11/2026
1.0.6 13 04/24/2026
1.0.5 16 04/23/2026
1.0.4 12 04/23/2026
1.0.3 12 04/23/2026
1.0.2 17 04/18/2026
1.0.1 13 04/17/2026
1.0.0 12 04/17/2026