WappSync Phone Verify - Documentación de la API
Verificación telefónica en la que su usuario envía un código a nosotros por WhatsApp - sin escribir OTP, un toque, totalmente verificado.
Contenido
Cómo Funciona
Usted solicita un código
Su backend llama a nuestra API con el número de teléfono del usuario. Devolvemos un código único y el número de WhatsApp al que enviarlo.
El usuario lo envía por WhatsApp
Su usuario toca un enlace de WhatsApp - el código está predefinido. Sin escribir, sin copiar y pegar. Solo pulsa enviar.
Verificado al instante
Recibimos el mensaje, vemos desde qué número de WhatsApp se envió y su consulta devuelve auth=1 con el teléfono certificado. ¡Listo!
Obtenga sus credenciales
Regístrese en wappsync.com/mybusiness para recibir:
| Credencial | Descripción |
|---|---|
businessid | Su ID de negocio numérico |
businesskey | Su clave secreta - nunca la comparta, nunca la ponga en el código del lado del cliente |
businesskey se usa para la firma HMAC y nunca debe aparecer en JavaScript del navegador, en el código fuente HTML ni en el código de la aplicación móvil.Dos modos de verificación
Phone Verify funciona en dos modos, elegidos por solicitud. En ambos, su usuario envía el código a nosotros por WhatsApp - la única diferencia es qué número de teléfono se certifica.
confirm_phone (default) | verify_sender | |
|---|---|---|
| Usted envía | un número de teléfono N | nada (sin número) |
| Qué se certifica | que el llamante controla N específicamente | cualquier número que realmente devuelva el código |
| Uso típico | inicio de sesión / recuperación en un número registrado, protección contra el cambio de SIM | registro nuevo - «dame un teléfono real y demostrado para este cliente» |
| Entrega del código | Enviado por WhatsApp a N y mostrado en su interfaz (delivery: pushed_to_phone) | solo a través de usted (delivery: relay_by_business) - usted entrega el código a su cliente |
| Resultado de la consulta | sender_matches_intended le indica si el remitente es N | phoneEnc es el número certificado; sender_matches_intended se omite |
confirm_phone demuestra el control de un número declarado específico; verify_sender demuestra el control de algún número e informa cuál. Si su flujo necesita que «el número de esta cuenta realmente pertenece al usuario», quédese en confirm_phone con on_mismatch: reject.Teléfono corregido (on_mismatch): un caso común es que el cliente declare A pero envíe el código desde B (error de escritura / dual-SIM). En confirm_phone, reject (predeterminado) lo trata como un intento fallido; accept_sender certifica en su lugar al remitente real B (sender_matches_intended: false, phoneEnc = B), y su backend decide si acepta el número corregido B o solicita un OTP nuevo para A.
Solicitar un OTP inverso
Llame a esto desde su backend cuando necesite verificar a un usuario:
POSThttps://wappsync.com/phone-verify/api/getReverseOTP
Cuerpo de la solicitud (JSON)
| Campo | Tipo | Descripción |
|---|---|---|
businessid | int | Su ID de negocio |
businessref | string | Una referencia única que usted genera para esta solicitud de autenticación (por ej. bin2hex(random_bytes(4))) |
phone | string | El número de teléfono del usuario en formato internacional (+40755123456). Obligatorio en el modo confirm_phone; omitir o dejar vacío para verify_sender. |
mode | string | Opcional - confirm_phone (predeterminado cuando se proporciona un teléfono) o verify_sender (predeterminado cuando se omite el teléfono) |
on_mismatch | string | Opcional (solo confirm_phone) - reject (predeterminado) o accept_sender (certifica al remitente real si el código llega desde un número diferente) |
ts | int | Marca de tiempo Unix actual |
hmac | string | Firma HMAC-SHA256 |
Firma HMAC
Firme esta cadena con su businesskey:
{businessid}|{businessref}|{phone}|{ts}
En el modo verify_sender no hay teléfono, por lo que la cadena firmada es {businessid}|{businessref}||{ts} (casilla central vacía).
Ejemplo:
$hmac = hash_hmac('sha256', "{$businessid}|{$businessref}|{$phone}|{$ts}", $businesskey);
Respuesta
| Campo | Tipo | Descripción |
|---|---|---|
status | string | "ok" or "error" |
reverseotp_display | string | El código que envía su usuario, por ej. "2FA-828161" |
waphone | string | El número de WhatsApp AL QUE su usuario envía el código |
logincode | string | Código interno para usar al consultar |
in_ts | int | Marca de tiempo para la solicitud de consulta |
out_ts | int | Marca de tiempo de la respuesta |
otp_valid_minutes | int | Cuánto tiempo es válido el código (predeterminado 10) |
mode | string | El modo activo: confirm_phone o verify_sender |
delivery | string | pushed_to_phone (confirm_phone: el código también se envía por WhatsApp al teléfono declarado) o relay_by_business (verify_sender: usted debe entregar el código a su cliente) |
hmac | string | HMAC de la respuesta - verifique con su businesskey |
Verificar el HMAC de la respuesta against {businessid}|{businessref}|{logincode}|{reverseotp_display}|{waphone}|{out_ts}.
Qué mostrar a su usuario
Muestre el código y el número de teléfono. Use un enlace whatsapp://send para que el usuario pueda enviarlo con un solo toque:
<a href="whatsapp://send?phone=40725296320&text=2FA-828161">
💬 Send Code via WhatsApp
</a>
Consultar la verificación
Consultar cada 2-3 segundos hasta que el usuario esté verificado:
POSThttps://wappsync.com/phone-verify/api/getReverseOTPStatus
Cuerpo de la solicitud (JSON)
| Campo | Tipo | Descripción |
|---|---|---|
businessid | int | Su ID de negocio |
businessref | string | La misma referencia de la llamada anterior |
logincode | string | El logincode de la respuesta de getReverseOTP |
in_ts | int | El ts original de la solicitud getReverseOTP |
hmac | string | HMAC-SHA256 de {businessid}|{businessref}|{logincode}|{in_ts} |
Respuesta
| Campo | Tipo | Significado |
|---|---|---|
used | 0 | ⏳ Aún esperando - vuelva a consultar |
used | 1 | ✅ Recién verificado - la primera consulta que ve esto lo consume |
used | 2 | 🔒 Ya consumido por una consulta anterior |
auth | 1 | ¡El usuario está verificado - inicie su sesión! |
phoneEnc | string | Teléfono certificado cifrado con AES-256-CBC (solo cuando auth=1) - el remitente cuando se recibió un código (idéntico a su número declarado en confirm_phone estricto) |
tries | int | Se cuentan los intentos fallidos (código incorrecto o teléfono incorrecto) |
expired | bool | true cuando hay demasiados intentos fallidos (intentos ≥ 4) - solicite un OTP nuevo |
mode | string | El modo de esta solicitud: confirm_phone o verify_sender |
sender_matches_intended | bool | confirm_phone solamente, una vez recibido un código - true = el remitente es el número que pidió verificar; false = se certificó un remitente corregido. Se omite en verify_sender. |
out_ts | int | Marca de tiempo de la respuesta |
hmac | string | HMAC - verifique con {businessid}|{businessref}|{logincode}|{used}|{phoneEnc}|{out_ts} |
Cuando auth === 1, se recibió un código válido. Descifre phoneEnc para obtener el teléfono certificado (compruebe sender_matches_intended en el modo confirm_phone) y luego inicie la sesión del usuario.
Descifrar el número de teléfono
Cuando auth=1, phoneEnc contiene el teléfono certificado (el remitente que se demostró) cifrado con AES-256-CBC usando su businesskey:
function decryptPhoneAES($encryptedData, $businesskey) {
$method = 'AES-256-CBC';
$key = hash('sha256', $businesskey, true);
$enc = str_replace(['-', '_'], ['+', '/'], $encryptedData);
$enc = str_pad($enc, strlen($enc) % 4 === 0
? strlen($enc) : strlen($enc) + 4 - strlen($enc) % 4,
'=', STR_PAD_RIGHT);
$decoded = base64_decode($enc);
$iv = substr($decoded, 0, 16);
$ciphertext = substr($decoded, 16);
return openssl_decrypt($ciphertext, $method, $key, OPENSSL_RAW_DATA, $iv);
}
$phone = decryptPhoneAES($response['phoneEnc'], $businesskey);
// Store this phone - it's the user's verified identity
Ejemplo de frontend
La única interfaz que necesita - muéstrela después de que su backend llame a getReverseOTP:
<div style="text-align:center; max-width:400px; margin:auto;">
<p>Send this exact code via WhatsApp from your phone:</p>
<div style="font-size:28px; font-weight:bold; color:#25D366;
letter-spacing:3px;">
2FA-828161
</div>
<p>to <strong>+40 725 296 320</strong></p>
<a href="whatsapp://send?phone=40725296320&text=2FA-828161"
style="display:inline-block; padding:14px 30px; background:#25D366;
color:white; font-size:18px; font-weight:bold; border-radius:8px;
text-decoration:none; margin-top:12px;">
💬 Send via WhatsApp
</a>
<p style="color:#888; font-size:13px; margin-top:12px;">
⏱ Code expires in 10 minutes - no typing required!
</p>
</div>
Ejemplo completo en PHP
Listo para copiar y pegar. Reemplace YOUR_BUSINESS_ID y 'your-secret-key' con los valores de wappsync.com/mybusiness.
<?php
$businessid = YOUR_BUSINESS_ID; // from wappsync.com/mybusiness
$businesskey = 'your-secret-key'; // from wappsync.com/mybusiness - keep server-side
// ── Step 1: Request reverse OTP ──
$businessref = bin2hex(random_bytes(4));
$phone = $_POST['phone']; // user's phone from your form
$ts = time();
$hmac = hash_hmac('sha256',
"{$businessid}|{$businessref}|{$phone}|{$ts}",
$businesskey
);
$response = callAPI('https://wappsync.com/phone-verify/api/getReverseOTP', [
'businessid' => $businessid,
'businessref' => $businessref,
'phone' => $phone,
'ts' => $ts,
'hmac' => $hmac,
]);
$data = json_decode($response, true);
$code = $data['reverseotp_display']; // "2FA-828161"
$waphone = $data['waphone']; // "+40725296320"
$logincode = $data['logincode'];
$in_ts = $data['in_ts'];
// ── Show the code to your user (render frontend example above) ──
// ── Step 2: Poll until verified ──
$maxAttempts = 40; // ~2 minutes at 3s intervals
$attempt = 0;
do {
sleep(3);
$attempt++;
$hmac = hash_hmac('sha256',
"{$businessid}|{$businessref}|{$logincode}|{$in_ts}",
$businesskey
);
$response = callAPI(
'https://wappsync.com/phone-verify/api/getReverseOTPStatus',
[
'businessid' => $businessid,
'businessref' => $businessref,
'logincode' => $logincode,
'in_ts' => $in_ts,
'hmac' => $hmac,
]
);
$data = json_decode($response, true);
} while (($data['auth'] ?? 0) !== 1 && $attempt < $maxAttempts);
if (($data['auth'] ?? 0) === 1) {
// ✅ Verified - log the user in!
$phone = decryptPhoneAES($data['phoneEnc'], $businesskey);
$_SESSION['user_phone'] = $phone;
header('Location: /dashboard');
exit;
} else {
echo "Verification timed out. Please try again.";
}
// ── Helper ──
function callAPI($url, $payload) {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
return $response;
}
verify_sender: envíe phone => '' (y opcionalmente mode => 'verify_sender') - no se envía nada por WhatsApp, así que usted mismo debe transmitir el reverseotp_display a su cliente. Con auth=1, phoneEnc se descifra al número certificado del remitente; no hay sender_matches_intended que comprobar.
accept_sender: para confirm_phone, añada 'on_mismatch' => 'accept_sender'. Si auth=1 y sender_matches_intended es false, el número certificado difiere del solicitado - acéptelo como número corregido o solicite un OTP nuevo para el número que realmente necesita.
Gestión de errores
La API devuelve "status": "error" con un message legible:
| HTTP | message | Cuándo |
|---|---|---|
| 400 | Missing required parameters | Olvidó un campo obligatorio |
| 400 | A phone is required in confirm_phone mode | confirm_phone solicitado sin un teléfono |
| 400 | Invalid mode / Invalid on_mismatch | Valor de enum desconocido |
| 400 | The phone number is invalid or not a mobile number. | Formato de teléfono incorrecto |
| 403 | Unknown or invalid business ID | businessid incorrecto |
| 403 | Invalid HMAC | La firma no coincide - compruebe la cadena de firma |
| 409 | This request was already used. | businessref+in_ts duplicado |
Seguridad
| 🔐 HMAC-signed | Cada solicitud y respuesta se verifica con su businesskey |
| 📱 Coincidencia de teléfono según el modo | confirm_phone verifica que el remitente coincide con el número solicitado; verify_sender / accept_sender solo certifican al remitente real - nunca un número que no envió nada |
| 🎯 Códigos en vivo únicos | Dos códigos pendientes no comparten valor, por lo que un código entrante se asigna de forma determinista a una sola solicitud |
| 🔂 Single-use | Los códigos pasan por 0→1→2, nunca reutilizables |
| ⏱ Time-limited | Los códigos expiran después de 10 minutos, máximo 4 intentos de entrega |
| 🔒 Sin secretos de cliente | Su businesskey permanece en su servidor; nunca en el código del navegador |
| 🛡 Cifrado AES-256 | Los números de teléfono en las respuestas se cifran con su businesskey |
Pruébelo en vivo
🎮 Flujo de inicio de sesión de la demo - una demo funcional que muestra la experiencia completa del usuario.
🧪 Herramienta de prueba de API - requiere su propio businessid y businesskey de wappsync.com/mybusiness.