656 lines
46 KiB
Plaintext
656 lines
46 KiB
Plaintext
<!-- Docs layout: sidebar + content -->
|
||
<div class="mx-auto flex max-w-7xl gap-10 px-5 py-12 sm:px-8">
|
||
|
||
<!-- ============ SIDEBAR ============ -->
|
||
<aside class="docs-sidebar hidden w-64 shrink-0 lg:block">
|
||
<div class="sticky top-24">
|
||
<p class="font-mono-tech px-3 text-[10px] uppercase tracking-[0.25em] text-slate-500">On this page</p>
|
||
<nav class="mt-4 flex flex-col gap-0.5 border-l border-white/10 text-sm">
|
||
<a href="#getting-started" data-target="getting-started" class="-ml-px border-l-2 border-transparent px-3 py-1.5 text-slate-400">Getting Started</a>
|
||
<a href="#authentication" data-target="authentication" class="-ml-px border-l-2 border-transparent px-3 py-1.5 text-slate-400">Authentication</a>
|
||
<a href="#rate-limits" data-target="rate-limits" class="-ml-px border-l-2 border-transparent px-3 py-1.5 text-slate-400">Rate Limits</a>
|
||
<a href="#errors" data-target="errors" class="-ml-px border-l-2 border-transparent px-3 py-1.5 text-slate-400">Error Codes</a>
|
||
|
||
<p class="font-mono-tech mt-5 px-3 text-[10px] uppercase tracking-[0.25em] text-slate-500">Client API</p>
|
||
<a href="#send-sms" data-target="send-sms" class="-ml-px border-l-2 border-transparent px-3 py-1.5 text-slate-400">Send SMS</a>
|
||
<a href="#sms-status" data-target="sms-status" class="-ml-px border-l-2 border-transparent px-3 py-1.5 text-slate-400">Check SMS Status</a>
|
||
<a href="#received-sms" data-target="received-sms" class="-ml-px border-l-2 border-transparent px-3 py-1.5 text-slate-400">List Received SMS</a>
|
||
<a href="#otp-send" data-target="otp-send" class="-ml-px border-l-2 border-transparent px-3 py-1.5 text-slate-400">Send OTP</a>
|
||
<a href="#otp-verify" data-target="otp-verify" class="-ml-px border-l-2 border-transparent px-3 py-1.5 text-slate-400">Verify OTP</a>
|
||
<a href="#admin-stats" data-target="admin-stats" class="-ml-px border-l-2 border-transparent px-3 py-1.5 text-slate-400">Admin Stats</a>
|
||
<a href="#admin-gateways" data-target="admin-gateways" class="-ml-px border-l-2 border-transparent px-3 py-1.5 text-slate-400">Admin Gateways</a>
|
||
|
||
<p class="font-mono-tech mt-5 px-3 text-[10px] uppercase tracking-[0.25em] text-slate-500">Gateway API</p>
|
||
<a href="#gw-register" data-target="gw-register" class="-ml-px border-l-2 border-transparent px-3 py-1.5 text-slate-400">Register Gateway</a>
|
||
<a href="#gw-heartbeat" data-target="gw-heartbeat" class="-ml-px border-l-2 border-transparent px-3 py-1.5 text-slate-400">Heartbeat</a>
|
||
<a href="#gw-sms-received" data-target="gw-sms-received" class="-ml-px border-l-2 border-transparent px-3 py-1.5 text-slate-400">Report Inbound SMS</a>
|
||
<a href="#gw-sms-status" data-target="gw-sms-status" class="-ml-px border-l-2 border-transparent px-3 py-1.5 text-slate-400">Report Delivery</a>
|
||
|
||
<p class="font-mono-tech mt-5 px-3 text-[10px] uppercase tracking-[0.25em] text-slate-500">Realtime</p>
|
||
<a href="#websocket" data-target="websocket" class="-ml-px border-l-2 border-transparent px-3 py-1.5 text-slate-400">WebSocket Channel</a>
|
||
<a href="#webhooks" data-target="webhooks" class="-ml-px border-l-2 border-transparent px-3 py-1.5 text-slate-400">Webhooks</a>
|
||
</nav>
|
||
</div>
|
||
</aside>
|
||
|
||
<!-- ============ CONTENT ============ -->
|
||
<div class="min-w-0 flex-1 space-y-20">
|
||
|
||
<!-- Hero -->
|
||
<section id="top">
|
||
<p class="font-mono-tech text-[11px] uppercase tracking-[0.3em] text-sky-400/80">Reference</p>
|
||
<h1 class="font-display mt-3 text-5xl font-extrabold tracking-tight text-white">API Documentation</h1>
|
||
<p class="mt-4 max-w-2xl text-lg text-slate-400">Every endpoint, parameter, and response for the MySMSAPio SMS Gateway. All routes are prefixed with <code class="rounded bg-white/5 px-2 py-0.5 font-mono-tech text-sm text-sky-300">/api/v1</code>.</p>
|
||
</section>
|
||
|
||
<!-- ============ GETTING STARTED ============ -->
|
||
<section id="getting-started" class="scroll-mt-24">
|
||
<h2 class="font-display text-3xl font-bold text-white">Getting Started</h2>
|
||
<div class="mt-6 space-y-4 text-slate-300">
|
||
<p>The MySMSAPio API is a JSON REST API. There are two classes of consumer:</p>
|
||
<ul class="ml-5 list-disc space-y-2 text-slate-400">
|
||
<li><span class="font-semibold text-slate-200">Client applications</span> — send SMS, generate/verify OTPs, read inbound messages, and query admin stats. Authenticated with a <code class="font-mono-tech text-sky-300">api_live_…</code> key.</li>
|
||
<li><span class="font-semibold text-slate-200">Gateway devices</span> — Android phones that register, send heartbeats, report inbound SMS and delivery receipts. Authenticated with a <code class="font-mono-tech text-sky-300">gw_live_…</code> key.</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="mt-6 grid gap-4 sm:grid-cols-2">
|
||
<div class="rounded-xl glass p-5">
|
||
<p class="font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Base URL</p>
|
||
<code class="mt-1 block font-mono-tech text-sm text-sky-300">https://your-host/api/v1</code>
|
||
</div>
|
||
<div class="rounded-xl glass p-5">
|
||
<p class="font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Content-Type</p>
|
||
<code class="mt-1 block font-mono-tech text-sm text-sky-300">application/json</code>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============ AUTHENTICATION ============ -->
|
||
<section id="authentication" class="scroll-mt-24">
|
||
<h2 class="font-display text-3xl font-bold text-white">Authentication</h2>
|
||
<p class="mt-4 text-slate-400">All authenticated endpoints expect an API key in the <code class="font-mono-tech text-sky-300">Authorization</code> header as a Bearer token. Keys are SHA-256 hashed at rest — the raw key is only shown once at creation.</p>
|
||
|
||
<div class="mt-6 grid gap-4 sm:grid-cols-2">
|
||
<div class="rounded-xl glass p-5">
|
||
<div class="flex items-center gap-2">
|
||
<i class="fas fa-key text-emerald-400"></i>
|
||
<span class="font-semibold text-white">Client key</span>
|
||
</div>
|
||
<code class="mt-2 block font-mono-tech text-xs text-emerald-300">api_live_<64 hex chars></code>
|
||
<p class="mt-2 text-xs text-slate-400">Permissions-based (send_sms, receive_sms…). Can expire.</p>
|
||
</div>
|
||
<div class="rounded-xl glass p-5">
|
||
<div class="flex items-center gap-2">
|
||
<i class="fas fa-mobile-screen text-sky-400"></i>
|
||
<span class="font-semibold text-white">Gateway key</span>
|
||
</div>
|
||
<code class="mt-2 block font-mono-tech text-xs text-sky-300">gw_live_<64 hex chars></code>
|
||
<p class="mt-2 text-xs text-slate-400">Bound to a single gateway device. Must be active.</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="code-block mt-6 rounded-xl p-4 text-xs leading-relaxed">
|
||
<span class="tok-com"># Bearer token in the Authorization header</span><br>
|
||
<span class="tok-key">curl</span> https://your-host/api/v1/sms/received \<br>
|
||
-H <span class="tok-str">"Authorization: Bearer api_live_..."</span>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============ RATE LIMITS ============ -->
|
||
<section id="rate-limits" class="scroll-mt-24">
|
||
<h2 class="font-display text-3xl font-bold text-white">Rate Limits</h2>
|
||
<p class="mt-4 text-slate-400">Limits are enforced via Redis and return <code class="font-mono-tech text-sky-300">429 Too Many Requests</code> with a <code class="font-mono-tech text-sky-300">retry_after</code> hint.</p>
|
||
<div class="mt-6 overflow-hidden rounded-xl glass">
|
||
<table class="min-w-full divide-y divide-white/10 text-sm">
|
||
<thead class="bg-white/5">
|
||
<tr>
|
||
<th class="px-5 py-3 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Resource</th>
|
||
<th class="px-5 py-3 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Limit</th>
|
||
<th class="px-5 py-3 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Window</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody class="divide-y divide-white/5">
|
||
<tr><td class="px-5 py-3 text-slate-300">Send SMS</td><td class="px-5 py-3 text-sky-300">100</td><td class="px-5 py-3 text-slate-400">per minute / API key</td></tr>
|
||
<tr><td class="px-5 py-3 text-slate-300">Send OTP</td><td class="px-5 py-3 text-sky-300">3</td><td class="px-5 py-3 text-slate-400">per hour / phone number</td></tr>
|
||
<tr><td class="px-5 py-3 text-slate-300">OTP verify attempts</td><td class="px-5 py-3 text-sky-300">3</td><td class="px-5 py-3 text-slate-400">per code</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============ ERRORS ============ -->
|
||
<section id="errors" class="scroll-mt-24">
|
||
<h2 class="font-display text-3xl font-bold text-white">Error Codes</h2>
|
||
<p class="mt-4 text-slate-400">Errors are returned as JSON with an <code class="font-mono-tech text-sky-300">error</code> field. Validation errors also include <code class="font-mono-tech text-sky-300">details</code>.</p>
|
||
<div class="mt-6 overflow-hidden rounded-xl glass">
|
||
<table class="min-w-full divide-y divide-white/10 text-sm">
|
||
<thead class="bg-white/5">
|
||
<tr>
|
||
<th class="px-5 py-3 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Status</th>
|
||
<th class="px-5 py-3 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Meaning</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody class="divide-y divide-white/5">
|
||
<% errors = [
|
||
["400", "Bad Request — missing required parameter"],
|
||
["401", "Unauthorized — missing or invalid API key"],
|
||
["403", "Forbidden — insufficient permissions"],
|
||
["404", "Not Found — resource does not exist"],
|
||
["409", "Conflict — e.g. gateway already registered"],
|
||
["422", "Unprocessable Entity — validation failure"],
|
||
["429", "Too Many Requests — rate limit exceeded"],
|
||
["500", "Internal Server Error"]
|
||
] %>
|
||
<% errors.each do |code, meaning| %>
|
||
<tr>
|
||
<td class="px-5 py-3"><span class="rounded-md bg-red-500/10 px-2 py-0.5 font-mono-tech text-xs text-red-300"><%= code %></span></td>
|
||
<td class="px-5 py-3 text-slate-400"><%= meaning %></td>
|
||
</tr>
|
||
<% end %>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ===================== CLIENT API DIVIDER ===================== -->
|
||
<div class="flex items-center gap-4">
|
||
<span class="h-px flex-1 bg-gradient-to-r from-transparent to-white/15"></span>
|
||
<span class="font-mono-tech text-[11px] uppercase tracking-[0.3em] text-sky-400/80">Client Application API</span>
|
||
<span class="h-px flex-1 bg-gradient-to-l from-transparent to-white/15"></span>
|
||
</div>
|
||
|
||
<!-- ============ SEND SMS ============ -->
|
||
<%# Reusable endpoint block. Data derived from the controller source. %>
|
||
<section id="send-sms" class="scroll-mt-24">
|
||
<div class="flex flex-wrap items-center gap-3">
|
||
<span class="rounded-md border px-2.5 py-1 font-mono-tech text-xs font-semibold badge-post">POST</span>
|
||
<code class="font-mono-tech text-lg text-white">/sms/send</code>
|
||
</div>
|
||
<h3 class="font-display mt-3 text-2xl font-bold text-white">Send an SMS</h3>
|
||
<p class="mt-2 text-slate-400">Queues an outbound message. The API returns immediately with a <code class="font-mono-tech text-sky-300">message_id</code>; delivery happens asynchronously via an available gateway. Rate limited to 100/min per API key.</p>
|
||
|
||
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Request body</p>
|
||
<div class="mt-2 overflow-hidden rounded-xl glass">
|
||
<table class="min-w-full divide-y divide-white/10 text-sm">
|
||
<thead class="bg-white/5"><tr>
|
||
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Parameter</th>
|
||
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Type</th>
|
||
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Required</th>
|
||
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Description</th>
|
||
</tr></thead>
|
||
<tbody class="divide-y divide-white/5">
|
||
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">to</td><td class="px-5 py-2 text-slate-400">string</td><td class="px-5 py-2 text-red-300">yes</td><td class="px-5 py-2 text-slate-400">E.164 phone number (validated via Phonelib)</td></tr>
|
||
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">message</td><td class="px-5 py-2 text-slate-400">string</td><td class="px-5 py-2 text-red-300">yes</td><td class="px-5 py-2 text-slate-400">Message body (max 1600 chars)</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Example request</p>
|
||
<div class="code-block mt-2 rounded-xl p-4 text-xs leading-relaxed">
|
||
<span class="tok-key">curl</span> -X POST https://your-host/api/v1/sms/send \<br>
|
||
-H <span class="tok-str">"Authorization: Bearer api_live_..."</span> \<br>
|
||
-H <span class="tok-str">"Content-Type: application/json"</span> \<br>
|
||
-d <span class="tok-str">'{"to":"+14155551234","message":"Your code is 482915"}'</span>
|
||
</div>
|
||
|
||
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Response · 202 Accepted</p>
|
||
<div class="code-block mt-2 rounded-xl p-4 text-xs leading-relaxed">
|
||
{<br>
|
||
<span class="tok-key">"success"</span>: <span class="tok-bool">true</span>,<br>
|
||
<span class="tok-key">"message_id"</span>: <span class="tok-str">"msg_3f8a9c1b2e7d4f60"</span>,<br>
|
||
<span class="tok-key">"status"</span>: <span class="tok-str">"queued"</span><br>
|
||
}
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============ SMS STATUS ============ -->
|
||
<section id="sms-status" class="scroll-mt-24">
|
||
<div class="flex flex-wrap items-center gap-3">
|
||
<span class="rounded-md border px-2.5 py-1 font-mono-tech text-xs font-semibold badge-get">GET</span>
|
||
<code class="font-mono-tech text-lg text-white">/sms/status/:message_id</code>
|
||
</div>
|
||
<h3 class="font-display mt-3 text-2xl font-bold text-white">Check SMS Status</h3>
|
||
<p class="mt-2 text-slate-400">Returns the current lifecycle state of a message, including timestamps.</p>
|
||
|
||
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Path parameters</p>
|
||
<div class="mt-2 overflow-hidden rounded-xl glass">
|
||
<table class="min-w-full divide-y divide-white/10 text-sm">
|
||
<thead class="bg-white/5"><tr>
|
||
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Parameter</th>
|
||
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Type</th>
|
||
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Description</th>
|
||
</tr></thead>
|
||
<tbody class="divide-y divide-white/5">
|
||
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">message_id</td><td class="px-5 py-2 text-slate-400">string</td><td class="px-5 py-2 text-slate-400">The ID returned when the message was created</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Response · 200 OK</p>
|
||
<div class="code-block mt-2 rounded-xl p-4 text-xs leading-relaxed">
|
||
{<br>
|
||
<span class="tok-key">"message_id"</span>: <span class="tok-str">"msg_3f8a9c1b2e7d4f60"</span>,<br>
|
||
<span class="tok-key">"status"</span>: <span class="tok-str">"delivered"</span>,<br>
|
||
<span class="tok-key">"sent_at"</span>: <span class="tok-str">"2026-08-01T12:00:00Z"</span>,<br>
|
||
<span class="tok-key">"delivered_at"</span>: <span class="tok-str">"2026-08-01T12:00:03Z"</span>,<br>
|
||
<span class="tok-key">"failed_at"</span>: <span class="tok-bool">null</span>,<br>
|
||
<span class="tok-key">"error_message"</span>: <span class="tok-bool">null</span><br>
|
||
}
|
||
</div>
|
||
<p class="mt-3 text-xs text-slate-500">Statuses: <code class="font-mono-tech text-sky-300">queued</code> → <code class="font-mono-tech text-sky-300">pending</code> → <code class="font-mono-tech text-sky-300">sent</code> → <code class="font-mono-tech text-sky-300">delivered</code> | <code class="font-mono-tech text-sky-300">failed</code></p>
|
||
</section>
|
||
|
||
<!-- ============ RECEIVED SMS ============ -->
|
||
<section id="received-sms" class="scroll-mt-24">
|
||
<div class="flex flex-wrap items-center gap-3">
|
||
<span class="rounded-md border px-2.5 py-1 font-mono-tech text-xs font-semibold badge-get">GET</span>
|
||
<code class="font-mono-tech text-lg text-white">/sms/received</code>
|
||
</div>
|
||
<h3 class="font-display mt-3 text-2xl font-bold text-white">List Received SMS</h3>
|
||
<p class="mt-2 text-slate-400">Paginated list of inbound messages, newest first. Optional filters by phone number or date.</p>
|
||
|
||
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Query parameters</p>
|
||
<div class="mt-2 overflow-hidden rounded-xl glass">
|
||
<table class="min-w-full divide-y divide-white/10 text-sm">
|
||
<thead class="bg-white/5"><tr>
|
||
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Parameter</th>
|
||
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Type</th>
|
||
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Default</th>
|
||
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Description</th>
|
||
</tr></thead>
|
||
<tbody class="divide-y divide-white/5">
|
||
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">phone_number</td><td class="px-5 py-2 text-slate-400">string</td><td class="px-5 py-2 text-slate-500">—</td><td class="px-5 py-2 text-slate-400">Filter by sender phone</td></tr>
|
||
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">since</td><td class="px-5 py-2 text-slate-400">datetime</td><td class="px-5 py-2 text-slate-500">—</td><td class="px-5 py-2 text-slate-400">Only messages after this time</td></tr>
|
||
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">limit</td><td class="px-5 py-2 text-slate-400">integer</td><td class="px-5 py-2 text-slate-500">50</td><td class="px-5 py-2 text-slate-400">Items per page</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Response · 200 OK</p>
|
||
<div class="code-block mt-2 rounded-xl p-4 text-xs leading-relaxed">
|
||
{<br>
|
||
<span class="tok-key">"messages"</span>: [<br>
|
||
{<br>
|
||
<span class="tok-key">"message_id"</span>: <span class="tok-str">"msg_7a2f..."</span>,<br>
|
||
<span class="tok-key">"from"</span>: <span class="tok-str">"+14155559999"</span>,<br>
|
||
<span class="tok-key">"message"</span>: <span class="tok-str">"STOP"</span>,<br>
|
||
<span class="tok-key">"received_at"</span>: <span class="tok-str">"2026-08-01T12:05:00Z"</span><br>
|
||
}<br>
|
||
],<br>
|
||
<span class="tok-key">"total"</span>: <span class="tok-num">1</span>,<br>
|
||
<span class="tok-key">"page"</span>: <span class="tok-num">1</span>,<br>
|
||
<span class="tok-key">"pages"</span>: <span class="tok-num">1</span><br>
|
||
}
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============ OTP SEND ============ -->
|
||
<section id="otp-send" class="scroll-mt-24">
|
||
<div class="flex flex-wrap items-center gap-3">
|
||
<span class="rounded-md border px-2.5 py-1 font-mono-tech text-xs font-semibold badge-post">POST</span>
|
||
<code class="font-mono-tech text-lg text-white">/otp/send</code>
|
||
</div>
|
||
<h3 class="font-display mt-3 text-2xl font-bold text-white">Send an OTP</h3>
|
||
<p class="mt-2 text-slate-400">Generates a 6-digit code, sends it via SMS, and returns the expiry time. Max 3 codes per phone per hour.</p>
|
||
|
||
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Request body</p>
|
||
<div class="mt-2 overflow-hidden rounded-xl glass">
|
||
<table class="min-w-full divide-y divide-white/10 text-sm">
|
||
<thead class="bg-white/5"><tr>
|
||
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Parameter</th>
|
||
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Type</th>
|
||
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Required</th>
|
||
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Default</th>
|
||
</tr></thead>
|
||
<tbody class="divide-y divide-white/5">
|
||
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">phone_number</td><td class="px-5 py-2 text-slate-400">string</td><td class="px-5 py-2 text-red-300">yes</td><td class="px-5 py-2 text-slate-500">—</td></tr>
|
||
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">purpose</td><td class="px-5 py-2 text-slate-400">string</td><td class="px-5 py-2 text-slate-500">no</td><td class="px-5 py-2 text-slate-400">authentication</td></tr>
|
||
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">expiry_minutes</td><td class="px-5 py-2 text-slate-400">integer</td><td class="px-5 py-2 text-slate-500">no</td><td class="px-5 py-2 text-slate-400">5</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Response · 200 OK</p>
|
||
<div class="code-block mt-2 rounded-xl p-4 text-xs leading-relaxed">
|
||
{<br>
|
||
<span class="tok-key">"success"</span>: <span class="tok-bool">true</span>,<br>
|
||
<span class="tok-key">"expires_at"</span>: <span class="tok-str">"2026-08-01T12:10:00Z"</span>,<br>
|
||
<span class="tok-key">"message_id"</span>: <span class="tok-str">"msg_9b1c..."</span><br>
|
||
}
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============ OTP VERIFY ============ -->
|
||
<section id="otp-verify" class="scroll-mt-24">
|
||
<div class="flex flex-wrap items-center gap-3">
|
||
<span class="rounded-md border px-2.5 py-1 font-mono-tech text-xs font-semibold badge-post">POST</span>
|
||
<code class="font-mono-tech text-lg text-white">/otp/verify</code>
|
||
</div>
|
||
<h3 class="font-display mt-3 text-2xl font-bold text-white">Verify an OTP</h3>
|
||
<p class="mt-2 text-slate-400">Validates the 6-digit code. After 3 failed attempts the code is expired. On success the code is marked verified and cannot be reused.</p>
|
||
|
||
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Request body</p>
|
||
<div class="mt-2 overflow-hidden rounded-xl glass">
|
||
<table class="min-w-full divide-y divide-white/10 text-sm">
|
||
<thead class="bg-white/5"><tr>
|
||
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Parameter</th>
|
||
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Type</th>
|
||
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Required</th>
|
||
</tr></thead>
|
||
<tbody class="divide-y divide-white/5">
|
||
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">phone_number</td><td class="px-5 py-2 text-slate-400">string</td><td class="px-5 py-2 text-red-300">yes</td></tr>
|
||
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">code</td><td class="px-5 py-2 text-slate-400">string</td><td class="px-5 py-2 text-red-300">yes</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="mt-5 grid gap-4 sm:grid-cols-2">
|
||
<div>
|
||
<p class="font-mono-tech text-[10px] uppercase tracking-wider text-emerald-400">Success · 200</p>
|
||
<div class="code-block mt-2 rounded-xl p-4 text-xs leading-relaxed">
|
||
{<br>
|
||
<span class="tok-key">"success"</span>: <span class="tok-bool">true</span>,<br>
|
||
<span class="tok-key">"verified"</span>: <span class="tok-bool">true</span><br>
|
||
}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="font-mono-tech text-[10px] uppercase tracking-wider text-red-400">Failure · 200</p>
|
||
<div class="code-block mt-2 rounded-xl p-4 text-xs leading-relaxed">
|
||
{<br>
|
||
<span class="tok-key">"success"</span>: <span class="tok-bool">false</span>,<br>
|
||
<span class="tok-key">"verified"</span>: <span class="tok-bool">false</span>,<br>
|
||
<span class="tok-key">"error"</span>: <span class="tok-str">"Invalid or expired OTP"</span>,<br>
|
||
<span class="tok-key">"attempts_remaining"</span>: <span class="tok-num">2</span><br>
|
||
}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============ ADMIN STATS ============ -->
|
||
<section id="admin-stats" class="scroll-mt-24">
|
||
<div class="flex flex-wrap items-center gap-3">
|
||
<span class="rounded-md border px-2.5 py-1 font-mono-tech text-xs font-semibold badge-get">GET</span>
|
||
<code class="font-mono-tech text-lg text-white">/admin/stats</code>
|
||
</div>
|
||
<h3 class="font-display mt-3 text-2xl font-bold text-white">Admin Statistics</h3>
|
||
<p class="mt-2 text-slate-400">Aggregate system metrics: gateway counts, message throughput, pending/failed counts, and OTP performance for today.</p>
|
||
|
||
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Response · 200 OK</p>
|
||
<div class="code-block mt-2 rounded-xl p-4 text-xs leading-relaxed">
|
||
{<br>
|
||
<span class="tok-key">"gateways"</span>: { <span class="tok-key">"total"</span>: <span class="tok-num">4</span>, <span class="tok-key">"active"</span>: <span class="tok-num">3</span>, <span class="tok-key">"online"</span>: <span class="tok-num">2</span>, <span class="tok-key">"offline"</span>: <span class="tok-num">2</span> },<br>
|
||
<span class="tok-key">"messages"</span>: {<br>
|
||
<span class="tok-key">"total_sent"</span>: <span class="tok-num">10234</span>, <span class="tok-key">"total_received"</span>: <span class="tok-num">8821</span>,<br>
|
||
<span class="tok-key">"sent_today"</span>: <span class="tok-num">142</span>, <span class="tok-key">"received_today"</span>: <span class="tok-num">98</span>,<br>
|
||
<span class="tok-key">"total_today"</span>: <span class="tok-num">240</span>, <span class="tok-key">"pending"</span>: <span class="tok-num">3</span>, <span class="tok-key">"failed_today"</span>: <span class="tok-num">1</span><br>
|
||
},<br>
|
||
<span class="tok-key">"otp"</span>: { <span class="tok-key">"sent_today"</span>: <span class="tok-num">56</span>, <span class="tok-key">"verified_today"</span>: <span class="tok-num">49</span>, <span class="tok-key">"verification_rate"</span>: <span class="tok-num">87.5</span> },<br>
|
||
<span class="tok-key">"timestamp"</span>: <span class="tok-str">"2026-08-01T12:00:00Z"</span><br>
|
||
}
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============ ADMIN GATEWAYS ============ -->
|
||
<section id="admin-gateways" class="scroll-mt-24">
|
||
<div class="flex flex-wrap items-center gap-3">
|
||
<span class="rounded-md border px-2.5 py-1 font-mono-tech text-xs font-semibold badge-get">GET</span>
|
||
<code class="font-mono-tech text-lg text-white">/admin/gateways</code>
|
||
</div>
|
||
<h3 class="font-display mt-3 text-2xl font-bold text-white">List Gateways</h3>
|
||
<p class="mt-2 text-slate-400">Returns every registered gateway device with live status and counters.</p>
|
||
|
||
<div class="mt-5 flex flex-wrap items-center gap-3">
|
||
<span class="rounded-md border px-2.5 py-1 font-mono-tech text-xs font-semibold badge-post">POST</span>
|
||
<code class="font-mono-tech text-lg text-white">/admin/gateways/:id/toggle</code>
|
||
</div>
|
||
<p class="mt-2 text-slate-400">Activate or deactivate a gateway. Toggling off stops it from receiving outbound commands.</p>
|
||
|
||
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Toggle response · 200 OK</p>
|
||
<div class="code-block mt-2 rounded-xl p-4 text-xs leading-relaxed">
|
||
{<br>
|
||
<span class="tok-key">"success"</span>: <span class="tok-bool">true</span>,<br>
|
||
<span class="tok-key">"gateway"</span>: { <span class="tok-key">"id"</span>: <span class="tok-num">1</span>, <span class="tok-key">"device_id"</span>: <span class="tok-str">"pixel-001"</span>, <span class="tok-key">"active"</span>: <span class="tok-bool">false</span> }<br>
|
||
}
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ===================== GATEWAY API DIVIDER ===================== -->
|
||
<div class="flex items-center gap-4">
|
||
<span class="h-px flex-1 bg-gradient-to-r from-transparent to-white/15"></span>
|
||
<span class="font-mono-tech text-[11px] uppercase tracking-[0.3em] text-sky-400/80">Gateway Device API</span>
|
||
<span class="h-px flex-1 bg-gradient-to-l from-transparent to-white/15"></span>
|
||
</div>
|
||
|
||
<!-- ============ GW REGISTER ============ -->
|
||
<section id="gw-register" class="scroll-mt-24">
|
||
<div class="flex flex-wrap items-center gap-3">
|
||
<span class="rounded-md border px-2.5 py-1 font-mono-tech text-xs font-semibold badge-post">POST</span>
|
||
<code class="font-mono-tech text-lg text-white">/gateway/register</code>
|
||
</div>
|
||
<h3 class="font-display mt-3 text-2xl font-bold text-white">Register a Gateway</h3>
|
||
<p class="mt-2 text-slate-400"><span class="rounded-md bg-emerald-500/10 px-2 py-0.5 font-mono-tech text-[10px] uppercase text-emerald-300">No auth</span> Registers an Android device and returns a fresh gateway API key. Returns <code class="font-mono-tech text-sky-300">409</code> if the device_id already exists.</p>
|
||
|
||
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Request body</p>
|
||
<div class="mt-2 overflow-hidden rounded-xl glass">
|
||
<table class="min-w-full divide-y divide-white/10 text-sm">
|
||
<thead class="bg-white/5"><tr>
|
||
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Parameter</th>
|
||
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Type</th>
|
||
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Required</th>
|
||
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Description</th>
|
||
</tr></thead>
|
||
<tbody class="divide-y divide-white/5">
|
||
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">device_id</td><td class="px-5 py-2 text-slate-400">string</td><td class="px-5 py-2 text-red-300">yes</td><td class="px-5 py-2 text-slate-400">Unique device identifier</td></tr>
|
||
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">name</td><td class="px-5 py-2 text-slate-400">string</td><td class="px-5 py-2 text-slate-500">no</td><td class="px-5 py-2 text-slate-400">Friendly name (defaults to device id prefix)</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Response · 201 Created</p>
|
||
<div class="code-block mt-2 rounded-xl p-4 text-xs leading-relaxed">
|
||
{<br>
|
||
<span class="tok-key">"success"</span>: <span class="tok-bool">true</span>,<br>
|
||
<span class="tok-key">"api_key"</span>: <span class="tok-str">"gw_live_a1b2c3..."</span>,<br>
|
||
<span class="tok-key">"device_id"</span>: <span class="tok-str">"pixel-001"</span>,<br>
|
||
<span class="tok-key">"websocket_url"</span>: <span class="tok-str">"wss://your-host/cable"</span><br>
|
||
}
|
||
</div>
|
||
<p class="mt-3 rounded-lg border border-amber-400/20 bg-amber-500/5 px-4 py-2 text-xs text-amber-200/80"><i class="fas fa-triangle-exclamation"></i> Save <code class="font-mono-tech">api_key</code> immediately — it is never shown again.</p>
|
||
</section>
|
||
|
||
<!-- ============ GW HEARTBEAT ============ -->
|
||
<section id="gw-heartbeat" class="scroll-mt-24">
|
||
<div class="flex flex-wrap items-center gap-3">
|
||
<span class="rounded-md border px-2.5 py-1 font-mono-tech text-xs font-semibold badge-post">POST</span>
|
||
<code class="font-mono-tech text-lg text-white">/gateway/heartbeat</code>
|
||
</div>
|
||
<h3 class="font-display mt-3 text-2xl font-bold text-white">Gateway Heartbeat</h3>
|
||
<p class="mt-2 text-slate-400">Refreshes the gateway's online status. A gateway is marked offline if no heartbeat arrives within 2 minutes (handled by <code class="font-mono-tech text-sky-300">CheckGatewayHealthJob</code>).</p>
|
||
|
||
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Optional body</p>
|
||
<div class="mt-2 overflow-hidden rounded-xl glass">
|
||
<table class="min-w-full divide-y divide-white/10 text-sm">
|
||
<thead class="bg-white/5"><tr>
|
||
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Parameter</th>
|
||
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Type</th>
|
||
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Description</th>
|
||
</tr></thead>
|
||
<tbody class="divide-y divide-white/5">
|
||
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">battery_level</td><td class="px-5 py-2 text-slate-400">integer</td><td class="px-5 py-2 text-slate-400">0–100</td></tr>
|
||
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">signal_strength</td><td class="px-5 py-2 text-slate-400">integer</td><td class="px-5 py-2 text-slate-400">dBm</td></tr>
|
||
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">messages_in_queue</td><td class="px-5 py-2 text-slate-400">integer</td><td class="px-5 py-2 text-slate-400">Pending outbound count on device</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Response · 200 OK</p>
|
||
<div class="code-block mt-2 rounded-xl p-4 text-xs leading-relaxed">
|
||
{ <span class="tok-key">"success"</span>: <span class="tok-bool">true</span>, <span class="tok-key">"pending_messages"</span>: <span class="tok-num">3</span> }
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============ GW SMS RECEIVED ============ -->
|
||
<section id="gw-sms-received" class="scroll-mt-24">
|
||
<div class="flex flex-wrap items-center gap-3">
|
||
<span class="rounded-md border px-2.5 py-1 font-mono-tech text-xs font-semibold badge-post">POST</span>
|
||
<code class="font-mono-tech text-lg text-white">/gateway/sms/received</code>
|
||
</div>
|
||
<h3 class="font-display mt-3 text-2xl font-bold text-white">Report Inbound SMS</h3>
|
||
<p class="mt-2 text-slate-400">Called by the gateway when a new SMS arrives on the device. Creates an inbound record and triggers <code class="font-mono-tech text-sky-300">ProcessInboundSmsJob</code> (which fires webhooks).</p>
|
||
|
||
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Request body</p>
|
||
<div class="mt-2 overflow-hidden rounded-xl glass">
|
||
<table class="min-w-full divide-y divide-white/10 text-sm">
|
||
<thead class="bg-white/5"><tr>
|
||
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Parameter</th>
|
||
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Type</th>
|
||
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Required</th>
|
||
</tr></thead>
|
||
<tbody class="divide-y divide-white/5">
|
||
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">sender</td><td class="px-5 py-2 text-slate-400">string</td><td class="px-5 py-2 text-red-300">yes</td></tr>
|
||
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">message</td><td class="px-5 py-2 text-slate-400">string</td><td class="px-5 py-2 text-red-300">yes</td></tr>
|
||
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">timestamp</td><td class="px-5 py-2 text-slate-400">datetime</td><td class="px-5 py-2 text-slate-500">no</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Response · 200 OK</p>
|
||
<div class="code-block mt-2 rounded-xl p-4 text-xs leading-relaxed">
|
||
{ <span class="tok-key">"success"</span>: <span class="tok-bool">true</span>, <span class="tok-key">"message_id"</span>: <span class="tok-str">"msg_7a2f..."</span> }
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============ GW SMS STATUS ============ -->
|
||
<section id="gw-sms-status" class="scroll-mt-24">
|
||
<div class="flex flex-wrap items-center gap-3">
|
||
<span class="rounded-md border px-2.5 py-1 font-mono-tech text-xs font-semibold badge-post">POST</span>
|
||
<code class="font-mono-tech text-lg text-white">/gateway/sms/status</code>
|
||
</div>
|
||
<h3 class="font-display mt-3 text-2xl font-bold text-white">Report Delivery Status</h3>
|
||
<p class="mt-2 text-slate-400">Updates an outbound message's status. On <code class="font-mono-tech text-sky-300">failed</code>, <code class="font-mono-tech text-sky-300">RetryFailedSmsJob</code> is enqueued if retries remain (< 3).</p>
|
||
|
||
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Request body</p>
|
||
<div class="mt-2 overflow-hidden rounded-xl glass">
|
||
<table class="min-w-full divide-y divide-white/10 text-sm">
|
||
<thead class="bg-white/5"><tr>
|
||
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Parameter</th>
|
||
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Type</th>
|
||
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Required</th>
|
||
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Values</th>
|
||
</tr></thead>
|
||
<tbody class="divide-y divide-white/5">
|
||
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">message_id</td><td class="px-5 py-2 text-slate-400">string</td><td class="px-5 py-2 text-red-300">yes</td><td class="px-5 py-2 text-slate-500">—</td></tr>
|
||
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">status</td><td class="px-5 py-2 text-slate-400">string</td><td class="px-5 py-2 text-red-300">yes</td><td class="px-5 py-2 text-slate-400">sent · delivered · failed</td></tr>
|
||
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">error_message</td><td class="px-5 py-2 text-slate-400">string</td><td class="px-5 py-2 text-slate-500">no</td><td class="px-5 py-2 text-slate-400">Reason for failure</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Response · 200 OK</p>
|
||
<div class="code-block mt-2 rounded-xl p-4 text-xs leading-relaxed">
|
||
{ <span class="tok-key">"success"</span>: <span class="tok-bool">true</span> }
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ===================== REALTIME DIVIDER ===================== -->
|
||
<div class="flex items-center gap-4">
|
||
<span class="h-px flex-1 bg-gradient-to-r from-transparent to-white/15"></span>
|
||
<span class="font-mono-tech text-[11px] uppercase tracking-[0.3em] text-sky-400/80">Realtime</span>
|
||
<span class="h-px flex-1 bg-gradient-to-l from-transparent to-white/15"></span>
|
||
</div>
|
||
|
||
<!-- ============ WEBSOCKET ============ -->
|
||
<section id="websocket" class="scroll-mt-24">
|
||
<div class="flex flex-wrap items-center gap-3">
|
||
<span class="rounded-md border px-2.5 py-1 font-mono-tech text-xs font-semibold badge-ws">WS</span>
|
||
<code class="font-mono-tech text-lg text-white">/cable</code>
|
||
</div>
|
||
<h3 class="font-display mt-3 text-2xl font-bold text-white">WebSocket Channel</h3>
|
||
<p class="mt-2 text-slate-400">Gateways maintain a persistent Action Cable connection for real-time command dispatch and status reporting. Connections authenticate with the gateway API key as a query param.</p>
|
||
|
||
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Connect</p>
|
||
<div class="code-block mt-2 rounded-xl p-4 text-xs leading-relaxed">
|
||
<span class="tok-key">wscat</span> -c <span class="tok-str">"wss://your-host/cable?api_key=gw_live_..."</span>
|
||
</div>
|
||
|
||
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Server → Gateway commands</p>
|
||
<div class="code-block mt-2 rounded-xl p-4 text-xs leading-relaxed">
|
||
{<br>
|
||
<span class="tok-key">"action"</span>: <span class="tok-str">"send_sms"</span>,<br>
|
||
<span class="tok-key">"message_id"</span>: <span class="tok-str">"msg_3f8a..."</span>,<br>
|
||
<span class="tok-key">"recipient"</span>: <span class="tok-str">"+14155551234"</span>,<br>
|
||
<span class="tok-key">"message"</span>: <span class="tok-str">"Hello!"</span><br>
|
||
}
|
||
</div>
|
||
|
||
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Gateway → Server events</p>
|
||
<div class="mt-2 overflow-hidden rounded-xl glass">
|
||
<table class="min-w-full divide-y divide-white/10 text-sm">
|
||
<thead class="bg-white/5"><tr>
|
||
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">action</th>
|
||
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Payload</th>
|
||
</tr></thead>
|
||
<tbody class="divide-y divide-white/5">
|
||
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">heartbeat</td><td class="px-5 py-2 text-slate-400">battery_level, signal_strength, messages_in_queue</td></tr>
|
||
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">delivery_report</td><td class="px-5 py-2 text-slate-400">message_id, status, error_message</td></tr>
|
||
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">message_received</td><td class="px-5 py-2 text-slate-400">sender, message, timestamp</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============ WEBHOOKS ============ -->
|
||
<section id="webhooks" class="scroll-mt-24">
|
||
<div class="flex flex-wrap items-center gap-3">
|
||
<span class="rounded-md border px-2.5 py-1 font-mono-tech text-xs font-semibold badge-post">WEBHOOK</span>
|
||
<code class="font-mono-tech text-lg text-white">Outbound HTTP POST</code>
|
||
</div>
|
||
<h3 class="font-display mt-3 text-2xl font-bold text-white">Webhooks</h3>
|
||
<p class="mt-2 text-slate-400">When configured, the API delivers signed event payloads to your URL via <code class="font-mono-tech text-sky-300">TriggerWebhookJob</code>. If a secret key is set, each request is signed with HMAC-SHA256.</p>
|
||
|
||
<div class="mt-6 grid gap-4 sm:grid-cols-3">
|
||
<% events = [
|
||
{ event: "sms_received", icon: "fa-arrow-down", color: "text-emerald-400", desc: "Inbound SMS arrived" },
|
||
{ event: "sms_sent", icon: "fa-paper-plane", color: "text-sky-400", desc: "Message dispatched to gateway" },
|
||
{ event: "sms_failed", icon: "fa-circle-xmark", color: "text-red-400", desc: "Delivery failed" }
|
||
] %>
|
||
<% events.each do |e| %>
|
||
<div class="rounded-xl glass p-5">
|
||
<i class="fas <%= e[:icon] %> <%= e[:color] %>"></i>
|
||
<code class="mt-2 block font-mono-tech text-sm text-white"><%= e[:event] %></code>
|
||
<p class="mt-1 text-xs text-slate-400"><%= e[:desc] %></p>
|
||
</div>
|
||
<% end %>
|
||
</div>
|
||
|
||
<p class="mt-6 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Example payload (sms_received)</p>
|
||
<div class="code-block mt-2 rounded-xl p-4 text-xs leading-relaxed">
|
||
{<br>
|
||
<span class="tok-key">"event"</span>: <span class="tok-str">"sms_received"</span>,<br>
|
||
<span class="tok-key">"message_id"</span>: <span class="tok-str">"msg_7a2f..."</span>,<br>
|
||
<span class="tok-key">"from"</span>: <span class="tok-str">"+14155559999"</span>,<br>
|
||
<span class="tok-key">"message"</span>: <span class="tok-str">"Confirm"</span>,<br>
|
||
<span class="tok-key">"received_at"</span>: <span class="tok-str">"2026-08-01T12:05:00Z"</span><br>
|
||
}
|
||
</div>
|
||
|
||
<p class="mt-6 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Verifying the signature</p>
|
||
<div class="code-block mt-2 rounded-xl p-4 text-xs leading-relaxed">
|
||
<span class="tok-com"># Ruby</span><br>
|
||
signature = OpenSSL::HMAC.hexdigest(<span class="tok-str">"SHA256"</span>, secret_key, payload.to_json)<br>
|
||
<span class="tok-com"># Compare with the X-Webhook-Signature header</span>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Back to top -->
|
||
<div class="pt-10 text-center">
|
||
<a href="#top" class="btn-ghost inline-flex items-center gap-2 rounded-xl px-5 py-2.5 text-sm font-semibold text-slate-200">
|
||
<i class="fas fa-arrow-up"></i> Back to top
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</div>
|