Key Takeaways
- ASP.NET OTP through an Android SMS gateway API is HttpClient + Bearer JSON, not a NuGet “Complete SDK.”
- Hash the challenge server-side. Put the digits only in the SMS body. TTL in your app.
- Hangfire (or any queue) must reuse Idempotency-Key = challenge id on retry. Guid.NewGuid() per attempt is the duplicate bug.
- Pin OTP to a dedicated deviceId. Do not share that SIM with campaigns.
- You bring the Android and operator credit. Pricing is devices plus send volume.
ASP.NET OTP SMS on an android sms gateway api is your backend calling HTTPS so a paired phone’s SIM delivers the code. Recipients see that MSISDN. Live fields: Developer Center. Samples: C# REST examples— not a Complete SDK.
Use-case: OTP verification. You bring the Android and airtime. We meter devices and volume.
Queue the send. Verify the hash. The radio is not your request thread.
ASP.NET talks HTTPS, the SIM talks GSM
Public send shape (transport only). Replace the per-call GUID with your challenge id:
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
public static class SmsGatewaySend
{
// Documented send endpoint — POST /api/v1/messages (Bearer JSON)
private const string SendUrl = "https://app.sms-gateway.app/api/v1/messages";
public static async Task SendAsync(string apiKey, string number, string message)
{
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
client.DefaultRequestHeaders.TryAddWithoutValidation("Idempotency-Key", Guid.NewGuid().ToString());
var json = "{\"to\":[\"" + number + "\"],\"text\":\"" + message + "\",\"type\":\"sms\"}";
using var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync(SendUrl, content);
var body = await response.Content.ReadAsStringAsync();
Console.WriteLine((int)response.StatusCode + " " + body);
}
}
202 accepted body — nothing has left the device yet:
{
"campaignId": 17,
"accepted": 1,
"scheduledAt": null,
"messages": [
{
"id": 41822,
"number": "+14155552671",
"text": "Your verification code is 481920",
"type": "sms",
"status": "Pending",
"campaignId": 17,
"deviceId": 3
}
]
}If Hangfire retries with a new Guid, the user gets two live codes. The sample’s NewGuid is a demo, not an OTP policy.
HttpClient guidance: Microsoft HttpClient guidelines.
How to send OTP from ASP.NET
- Generate 6 digits. Store hash + expiry + E.164.
- Enqueue a job with challenge id.
- POST /messages; Idempotency-Key = that id; pin deviceIds.
- User submits code; constant-time compare against hash.
Templates: OTP templates · idempotent send.
Hangfire vs in-request send
| In the MVC/minimal API action | Hangfire / worker | |
|---|---|---|
| Latency | User waits on GSM | HTTP returns; radio async |
| Retries | Easy to double-click | Same key on retry |
| Timeouts | IIS/Kestrel abort | Job visibility timeout you control |
Idempotency-Key = challenge id
New user-facing digits ⇒ new challenge ⇒ new key. Network retry of the same challenge ⇒ same key. Duplicate sends.
Twilio contrast
Aggregator: rented number + per-message vendor fee. This path: own SIM + operator cost + device/volume service fee. vs Twilio.
Pin the OTP device
OTP queue limits · Doze. Webhooks: webhooks.
Two bills
Free: 300 SMS lifetime. Developer: 25,000/year. Starter/Pro/Business uncap platform volume, devices 2/5/15. Pause on Free/Developer at allowance. No unlimited carrier SMS. Pricing.
Checklist
- Key in env; never in appsettings committed.
- Hash + TTL; GSM-7 body.
- Stable Idempotency-Key; dedicated OTP phone.
- Forced 504 in staging = one bubble.
Next steps
Send one staff OTP from Hangfire, then parse DLR in C#. Parse DLR · downloads.
Related product pages
Jump to the live product docs for this topic—not another long-form article.
- OTP and 2FA SMS on AndroidAuthentication flows
- SMS API documentationLive endpoint reference
- C# REST send samplesC# code examples
- device and SMS volume pricingPlans and allowances





