Verify Currency Send SMS PIN

Verify the SMS PIN to authorize a currency send. This endpoint validates the PIN sent to the sender's phone and, upon success, automatically sends the claim code to the receiver via SMS.

POST

/api/currency-sends/verify-sms

Verifies the SMS PIN and sends claim code to receiver

Authentication Required

Must include the same game secret key used to initiate the send:

X-Game-Secret-Key: your_game_secret_key_here

Request Body Parameters

ParameterTypeRequiredDescription
transaction_idstringYesTransaction ID returned from initiate-send
sms_pinstringYes6-digit PIN received via SMS

SMS PIN Requirements

PIN Format

  • • 6-digit numeric code
  • • Case-sensitive (numbers only)
  • • Unique per send attempt
  • • One-time use only

Time Limits

  • • PIN expires in 10 minutes
  • • Maximum 3 verification attempts
  • • Failed attempts count toward rate limits
  • • Expired PINs cannot be reused

Implementation Examples

UnityUnity C# SMS Verification

// This function MUST be called on your secure server, NOT the client.
public IEnumerator VerifySendSMSPin(string transactionId, string smsPin, 
    Action<string> onSuccess, Action<string> onError)
{
    string url = "https://invo.network/api/currency-sends/verify-sms";
    
    var requestData = new {
        transaction_id = transactionId,
        sms_pin = smsPin
    };
    string jsonBody = JsonUtility.ToJson(requestData);

    using (UnityWebRequest request = new UnityWebRequest(url, "POST"))
    {
        byte[] bodyRaw = System.Text.Encoding.UTF8.GetBytes(jsonBody);
        request.uploadHandler = new UploadHandlerRaw(bodyRaw);
        request.downloadHandler = new DownloadHandlerBuffer();
        
        request.SetRequestHeader("X-Game-Secret-Key", "your_game_secret_key_here");
        request.SetRequestHeader("Content-Type", "application/json");

        yield return request.SendWebRequest();

        if (request.result == UnityWebRequest.Result.Success)
        {
            onSuccess?.Invoke(request.downloadHandler.text);
        }
        else
        {
            onError?.Invoke(request.error + ": " + request.downloadHandler.text);
        }
    }
}

UnrealUnreal C++ SMS Verification

// This function MUST be called on your secure server.
void AYourGameMode::VerifySendSMSPin(const FString& TransactionId, const FString& SMSPin)
{
    TSharedPtr<FJsonObject> RequestObj = MakeShareable(new FJsonObject);
    RequestObj->SetStringField("transaction_id", TransactionId);
    RequestObj->SetStringField("sms_pin", SMSPin);

    FString RequestBody;
    TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&RequestBody);
    FJsonSerializer::Serialize(RequestObj.ToSharedRef(), Writer);

    TSharedRef<IHttpRequest> Request = FHttpModule::Get().CreateRequest();
    Request->SetURL("https://invo.network/api/currency-sends/verify-sms");
    Request->SetVerb("POST");
    Request->SetHeader("X-Game-Secret-Key", "your_game_secret_key_here");
    Request->SetHeader("Content-Type", "application/json");
    Request->SetContentAsString(RequestBody);
    
    Request->OnProcessRequestComplete().BindUObject(this, &AYourGameMode::OnSendSMSVerified);
    Request->ProcessRequest();
}

GodotGodot GDScript SMS Verification

# This function MUST be called on your secure server.
func verify_send_sms_pin(transaction_id: String, sms_pin: String):
    var url = "https://invo.network/api/currency-sends/verify-sms"
    var headers = [
        "X-Game-Secret-Key: your_game_secret_key_here",
        "Content-Type: application/json"
    ]
    var body = {
        "transaction_id": transaction_id,
        "sms_pin": sms_pin
    }
    
    http_request.request(url, headers, HTTPClient.METHOD_POST, JSON.stringify(body))

Request & Response Examples

Example Request

POST /api/currency-sends/verify-sms
Content-Type: application/json
X-Game-Secret-Key: your_game_secret_key_here

{
  "transaction_id": "txn_send123abc456",
  "sms_pin": "123456"
}

Guardian-approval-aware responses

If the original send was initiated by a minor account, this endpoint may return:

  • 202 GUARDIAN_APPROVAL_PENDING — guardian hasn't replied yet. The same call succeeds normally once approval lands; just keep polling.
  • 410 GUARDIAN_APPROVAL_REJECTED or GUARDIAN_APPROVAL_EXPIRED — terminal. Show a clear message; user must re-initiate.

Full state machine: Guardian Approval.

Success Response (200 OK)

{
  "status": "success",
  "message": "SMS verification successful. A claim code has been sent to the receiver.",
  "transaction_id": "txn_send123abc456",
  "claim_code": "KJMRS-47281",
  "new_balance": "750.00",
  "claim_instructions": {
    "message": "Claim code has been sent to the receiver via SMS.",
    "receiving_game_id": "42",
    "receiving_game_name": "Space Warriors",
    "claim_code_expires_at": "2024-06-25T14:30:00Z",
    "receiver_notified": true
  },
  "send_summary": {
    "amount_sent": "500.00",
    "net_amount_for_claim": "450.00",
    "fees_deducted": "50.00",
    "sender_current_available_balance": "750.00"
  },
  "order_id": "ord_send789def012"
}

Top-level new_balance is the canonical post-write balance — the sender's available balance, already debited by the reservation at initiate-send. Display this value rather than recomputing it client-side.

Error Response - Invalid PIN (400 Bad Request)

{
  "status": "error",
  "message": "Invalid SMS PIN.",
  "attempts_remaining": 2
}

Error Response - Expired PIN (400 Bad Request)

{
  "status": "error",
  "message": "SMS PIN is not valid: PIN expired."
}

Error Response - Rate Limited (429 Too Many Requests)

{
  "error": "sms_verification_blocked",
  "message": "Too many SMS verification attempts. Please try again in 1 hour.",
  "retry_after": 3600
}

Resend Verification PIN

POST

/api/currency-sends/resend-pin

Re-sends the same still-valid PIN if the first SMS didn't arrive

If the sender never received the first SMS, you can re-queue the same verification PIN for delivery. This does not generate a new PIN — the original code and its expiry are unchanged. Once the PIN has expired, re-initiate the send instead.

Authentication Required

Uses the same game secret key as initiate and verify:

X-Game-Secret-Key: your_game_secret_key_here

Request Body Parameters

ParameterTypeRequiredDescription
transaction_idstringYesTransaction ID returned from initiate-send. No sms_pin is sent — the original PIN is re-used.

Example Request

POST /api/currency-sends/resend-pin
Content-Type: application/json
X-Game-Secret-Key: your_game_secret_key_here

{
  "transaction_id": "txn_send123abc456"
}

Response Codes

StatusBodyMeaning
200{ "status": "resent" }The same PIN was re-queued for delivery.
429{ "error": "resend_cooldown", "retry_after": <seconds> }Requested too soon. Wait retry_after seconds before retrying (cooldown is 30s).
400{ "error": "pin_expired", "message": "Start a new transfer." }The PIN has already expired. No new PIN is minted — re-initiate the send instead.
400Wrong state / missing fieldSend is not pending verification, or transaction_id is missing.
404Not foundTransaction not found, or it belongs to another game.
401UnauthorizedBad or missing X-Game-Secret-Key.
503{ "error": "resend_unavailable" }Resend may be unavailable in some environments (e.g. sandbox today). Fall back to re-initiating the send.

Recommended integrator flow

  • PIN not received and not yet expired → call resend-pin (respect the 30s cooldown / retry_after).
  • On pin_expired or resend_unavailable (503) → re-initiate the send.

Rate Limiting

  • • 10 resends per hour per transaction
  • • 60 resends per hour per source IP
  • • Separate from the verify-sms rate limits
  • • 30-second cooldown between consecutive resends

What Happens After Successful Verification

Claim Code Generated & Sent

A unique claim code is generated and automatically sent via SMS to the receiver's phone number provided during send initiation.

"You have received 450.00 Gold from PlayerOne. Claim code: KJMRS-47281. Use this in Space Warriors. Expires in 24 hours."

Send Status Updated

The send status changes from "pending_pin_verification" to "pending_claim". The funds remain reserved in the sender's account until the send is either claimed or expires.

24-Hour Claim Window

The receiver has 24 hours to claim the currency using the claim code. If not claimed within this time, the send expires and the net amount (minus fees) is automatically returned to the sender's balance.

Automatic Receiver Notification

How Receivers Are Notified

Unlike cross-game transfers where the sender manually shares the claim code, currency sends automatically notify the receiver via SMS. This creates a seamless experience for player-to-player transactions.

SMS Content Includes:
  • • Amount and currency received
  • • Sender's display name
  • • Unique claim code
  • • Target game name for claiming
  • • 24-hour expiration notice

Note: The receiver doesn't need to have an account in the receiving game beforehand. If they don't exist as a player, a new account will be automatically created when they claim the currency.

Security Features

PIN Protection

  • • Hashed storage - PINs never stored in plain text
  • • Limited attempts - Maximum 3 verification tries
  • • Time-based expiry - 10 minute validity window
  • • One-time use - PIN becomes invalid after successful verification

Rate Limiting

  • • 35 verification attempts per hour per player
  • • 20 attempts per minute per transaction
  • • 175 attempts per hour per IP address
  • • Failed attempts trigger progressive blocking

Fraud Detection

  • • SMS verification spam detection
  • • Invalid PIN attempt tracking
  • • IP-based suspicious activity monitoring
  • • Automatic blocking for repeated failures

Transaction Integrity

  • • Funds remain reserved during verification
  • • Automatic cleanup of expired transactions
  • • Comprehensive audit trail
  • • Circuit breaker for service protection

Common Error Scenarios

HTTP 400 - Bad Request

  • Invalid SMS PIN: PIN doesn't match or has wrong format
  • PIN expired: More than 10 minutes have passed since initiation
  • Max attempts reached: 3 incorrect PIN attempts used up
  • Missing parameters: transaction_id or sms_pin not provided
  • Wrong send state: Send not in pending_pin_verification status

HTTP 404 - Not Found

  • Transaction not found: Invalid transaction_id or wrong game
  • Unauthorized access: Game secret key doesn't match send source

HTTP 429 - Too Many Requests

  • SMS verification spam: Too many verification attempts from player
  • Transaction rate limit: Too many attempts for specific transaction
  • IP rate limit: Too many requests from the same network
  • Invalid PIN blocking: Player blocked due to repeated invalid PINs

HTTP 500 - Server Error

  • Database connectivity: Temporary database issues
  • SMS service error: Unable to send claim code to receiver
  • Circuit breaker open: Service temporarily unavailable

Implementation Best Practices

User Experience

  • • Display remaining attempts when PIN verification fails
  • • Show countdown timer for PIN expiration
  • • Provide clear error messages for different failure scenarios
  • • If the PIN didn't arrive but hasn't expired, use resend-pin to re-send the same code; only re-initiate the send once the PIN has expired
  • • Show receiver notification status (SMS sent automatically)

Error Handling

  • • Parse error responses to show appropriate user messages
  • • Handle rate limiting with retry logic and user feedback
  • • Implement exponential backoff for server errors
  • • Log all API responses for debugging and monitoring
  • • Provide clear guidance when SMS delivery fails

Security

  • • Never log or store SMS PINs in your application
  • • Validate PIN format on client side before sending
  • • Clear PIN input fields after submission
  • • Use HTTPS for all API communications
  • • Implement proper session management

Send Completion

  • • Inform sender that receiver has been automatically notified
  • • Display claim code for sender's reference (optional)
  • • Show receiving game name and expiration time clearly
  • • Update sender's balance to reflect reserved amount
  • • Provide send tracking/history functionality