← Blog
Developer playbook · 16 分で読めます · Published 4月 2026 · Author Serhat Dogan

SMS-Activate 移行ハブ 2026: 開発者チェックリスト、API マッピング & 返金比較

SMS-Activate 移行ハブ 2026: 開発者チェックリスト、API マッピング & 返金比較

If you are still carrying handler_api.php calls in your repo pointed at sms-activate.org, this is the hub you open at 9pm on a Friday and close with a working integration before midnight. Real endpoint mapping, real code diffs, refund policy comparison, and the gotchas that will bite you between the line you change and the alert that wakes you up on Monday morning.

このプレイブックが存在する理由

「SMS-Activateはもう使えない、代替サービスはこちら」といった投稿の多くは、開発者の時間を最も消費する部分、つまりコードの部分を見落としています。新しいプロバイダーへのサインアップは5分で済みますが、長年稼働しており、テストされていないエッジケースやコストトラッカーが付随しているインテグレーションの書き換えには、あなたが思っているよりも時間がかかります。

2026年1月に互換性レイヤーをリリースした後、それを試したすべてのチームから同じ3つの質問を受けました。

  1. どのエンドポイントがそのまま使え、どれを手動で変更する必要がありますか?
  2. コードを書き直さずに、コスト追跡と返金フローを機能させ続けるにはどうすればよいですか?
  3. 何がサイレントに壊れ、1週間後に請求のサプライズとして現れますか?

このプレイブックは、これらの3つの質問に順番に答え、実行前に監査できるコピー&ペースト可能なコードを提供します。

48時間の振り返り:実際に何が起こったか

SMS-Activateは2025年12月29日にサービスを停止しました。予定されていたメンテナンスバナー、移行ツール、公開通知はありませんでした。ログインしようとしたユーザーは、サービスが永久に閉鎖されたことを示す単一のページにアクセスしました。APIは数時間以内にすべてエンドポイントで接続リセットを返しました。

3つのことが急速に起こりました。

その影響はまだ続いています。2026年4月現在、ロシアでは少額訴訟が係争中であり、凍結された残高の回収を目指す共同コミュニティ訴訟が少なくとも2件あります。それらのどれもあなたのコードには役立たないので、実際に修正できる部分に焦点を当てます。

パート1:API廃止マッピング

SMS-Activateは、https://sms-activate.org/stubs/handler_api.phpという単一の公開エンドポイントを提供していました。すべての操作は、そのURLのクエリ文字列パラメータでした。以下の表は、主要な各操作とVerifySMSの同等のものをマッピングしています。https://api.verifysms.app/compat/handler_api.phpの互換性レイヤーは、まったく同じクエリ文字列形式を受け入れます。

SMS-Activate アクション目的VerifySMS 互換レイヤーネイティブ VerifySMS API
getBalanceUSD残高をテキストで返す変更なく動作します。ACCESS_BALANCE:X.YYを返しますGET /v1/balanceはJSONを返します
getNumbersStatus国別の利用可能性動作します。レガシーマップ形式を返しますGET /v1/countries/availability
getNumberサービス用の番号をリースする動作します。ACCESS_NUMBER:id:+phoneを返しますPOST /v1/rentals
setStatusリースを確認またはキャンセルする動作します。ステータスコード1/3/6/8は同じように動作しますPOST /v1/rentals/{id}/status
getStatusSMS到着をポーリングする動作します。STATUS_WAIT_CODE、STATUS_OK:CODE、STATUS_WAIT_RETRYを返しますGET /v1/rentals/{id}
getPrices価格表を取得する動作します。レガシーJSON形式でVerifySMSの価格を返しますGET /v1/prices
getCountries国コードマップ動作します。レガシー数値IDとISO-3166コードの両方を返しますGET /v1/countries
getTopCountriesByServiceサービスごとのトップカントリーキャッシュされたSMS-Activateのランクではなく、リアルタイムのVerifySMSデータを返しますGET /v1/services/{id}/top-countries

あまり使用されなかった少数のSMS-Activateアクションは、1対1でマッピングされません。getRentServicesAndCountriesとロングリースレンタルAPIはSMS-Activate固有であり、互換性レイヤーはありません。それらを使用していた場合は、別途文書化されているVerifySMSのネイティブロングリースエンドポイントPOST /v1/rentals/longに移行する必要があります。

パート2:コード移行ウォークスルー

以下のスニペットは、1月に私たち自身のステージング環境に対してテストした正確な形式です。コンテキストスイッチなしで自分のコードに対して読み込めるように、意図的に単純に保っています。

Python (requests)

唯一必要な変更はベースURLです。APIを小さなクライアントモジュールにすでにラップしている場合、差分は1行です。

import os
import requests

# BEFORE
# BASE_URL = "https://sms-activate.org/stubs/handler_api.php"
# AFTER
BASE_URL = "https://api.verifysms.app/compat/handler_api.php"
API_KEY = os.environ["SMS_API_KEY"]

def get_number(service: str, country: int) -> tuple[str, str]:
    resp = requests.get(BASE_URL, params={
        "api_key": API_KEY,
        "action": "getNumber",
        "service": service,
        "country": country,
    }, timeout=30)
    resp.raise_for_status()
    # ACCESS_NUMBER:12345:+441234567890
    status, rental_id, phone = resp.text.split(":", 2)
    if status != "ACCESS_NUMBER":
        raise RuntimeError(f"unexpected response: {resp.text}")
    return rental_id, phone

def wait_for_code(rental_id: str, deadline_seconds: int = 180) -> str:
    import time
    start = time.monotonic()
    while time.monotonic() - start < deadline_seconds:
        resp = requests.get(BASE_URL, params={
            "api_key": API_KEY,
            "action": "getStatus",
            "id": rental_id,
        }, timeout=15).text
        if resp.startswith("STATUS_OK:"):
            return resp.split(":", 1)[1]
        time.sleep(4)
    # 返金を受けるために未使用としてマークする
    requests.get(BASE_URL, params={
        "api_key": API_KEY,
        "action": "setStatus",
        "status": 8,
        "id": rental_id,
    }, timeout=15)
    raise TimeoutError(f"no code after {deadline_seconds}s")

Node.js (axios)

import axios from "axios";

// BEFORE
// const BASE_URL = "https://sms-activate.org/stubs/handler_api.php";
// AFTER
const BASE_URL = "https://api.verifysms.app/compat/handler_api.php";
const API_KEY = process.env.SMS_API_KEY;

export async function getNumber(service, country) {
  const { data } = await axios.get(BASE_URL, {
    params: { api_key: API_KEY, action: "getNumber", service, country },
    timeout: 30_000,
  });
  const [status, rentalId, phone] = data.split(":");
  if (status !== "ACCESS_NUMBER") {
    throw new Error(`unexpected response: ${data}`);
  }
  return { rentalId, phone };
}

export async function waitForCode(rentalId, deadlineMs = 180_000) {
  const start = Date.now();
  while (Date.now() - start < deadlineMs) {
    const { data } = await axios.get(BASE_URL, {
      params: { api_key: API_KEY, action: "getStatus", id: rentalId },
      timeout: 15_000,
    });
    if (data.startsWith("STATUS_OK:")) return data.split(":")[1];
    await new Promise((r) => setTimeout(r, 4000));
  }
  await axios.get(BASE_URL, {
    params: { api_key: API_KEY, action: "setStatus", status: 8, id: rentalId },
    timeout: 15_000,
  });
  throw new Error(`no code after ${deadlineMs}ms`);
}

PHP (curl)

<?php
// BEFORE
// const BASE_URL = "https://sms-activate.org/stubs/handler_api.php";
// AFTER
const BASE_URL = "https://api.verifysms.app/compat/handler_api.php";

function sms_call(array $params): string {
    $params["api_key"] = getenv("SMS_API_KEY");
    $url = BASE_URL . "?" . http_build_query($params);
    $ch  = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 30);
    $body = curl_exec($ch);
    curl_close($ch);
    return $body;
}

function get_number(string $service, int $country): array {
    $resp = sms_call(["action" => "getNumber", "service" => $service, "country" => $country]);
    [$status, $id, $phone] = explode(":", $resp, 3);
    if ($status !== "ACCESS_NUMBER") {
        throw new RuntimeException("unexpected: $resp");
    }
    return ["id" => $id, "phone" => $phone];
}

Go (net/http)

package sms

import (
    "errors"
    "fmt"
    "io"
    "net/http"
    "net/url"
    "os"
    "strings"
    "time"
)

// BEFORE
// const baseURL = "https://sms-activate.org/stubs/handler_api.php"
// AFTER
const baseURL = "https://api.verifysms.app/compat/handler_api.php"

func call(params url.Values) (string, error) {
    params.Set("api_key", os.Getenv("SMS_API_KEY"))
    req, _ := http.NewRequest("GET", baseURL+"?"+params.Encode(), nil)
    client := &http.Client{Timeout: 30 * time.Second}
    resp, err := client.Do(req)
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()
    body, _ := io.ReadAll(resp.Body)
    return string(body), nil
}

func GetNumber(service string, country int) (id, phone string, err error) {
    body, err := call(url.Values{
        "action":  {"getNumber"},
        "service": {service},
        "country": {fmt.Sprint(country)},
    })
    if err != nil {
        return "", "", err
    }
    parts := strings.SplitN(body, ":", 3)
    if len(parts) != 3 || parts[0] != "ACCESS_NUMBER" {
        return "", "", errors.New("unexpected: " + body)
    }
    return parts[1], parts[2], nil
}

Goの例は、サードパーティ製クライアントなしで意図的に書かれているため、依存関係を追加せずに最小限のサービスにドロップできます。他のすべての言語でも同じパターンが適用されます。URLを交換し、残りはそのままにし、既存のエラー処理を引き継ぎます。

パート3:あなたを苦しめる可能性のある落とし穴

これらは、互換性レイヤーがSMS-Activateの癖に忠実であるにもかかわらず、しばらくコードに触れていないと驚くような癖がある場所です。

国IDはISOコードではありません

SMS-Activateは独自の順序で国に番号を付けていました:ロシアは0、アメリカは187、インドネシアは6などです。インテグレーションにこれらのマジックナンバーがハードコーディングされている場合、互換性レイヤーでも引き続き機能します。新しいコードを書く場合は、ISO-3166 alpha-2形式(RUUSID)を優先してください。互換性レイヤーもこれを受け入れます。両方のスタイルを同じ呼び出しサイトで混在させないでください。後でデバッグするのが大変になります。

ステータスコード3と6の違い

setStatusアクションコード3は、SMS-Activateの世界では「別のSMSをリクエストする」ことを意味し、コード6は「コードを有効として受け入れる」ことを意味しました。これらの2つのコードは、急いでいるときに簡単に交換でき、請求結果は反対です。3は課金を継続し、6は検証の成功を確認します。互換性レイヤーも同様に動作します。コード内でsetStatusをgrepし、コード6を受け入れるブランチが検証が成功したことを確認した後にのみ実行されるようにしてください。

タイムアウトとサーキットブレーカー

SMS-Activateは負荷の下で、HTTPエラーの代わりに空のボディを持つ200を返すことがありました。防御的なクライアントは、呼び出しをタイムアウトでラップし、空のボディを再試行信号として扱います。VerifySMSは互換性レイヤーで空のボディを返すことはありません。クライアントがまだ空を再試行として扱っている場合、再試行が別のレンタルIDにヒットするため、不安定なネットワークで予算を浪費することになります。より安全なパターンは、既知の応答プレフィックス(ACCESS_STATUS_BAD_)を確認し、それ以外のものは一時的なものではなくハードフェイルとして扱うことです。

レート制限がキーごとからIPごとへ移行

SMS-Activateのレート制限はAPIトークンによってキー付けされていました。VerifySMSのレート制限は、APIトークンとソースIPアドレスの組み合わせによってキー付けされています。これは、1つのキーをボットネット全体で共有するスクレイパー スクリプトからの多くの不正使用を目にするためです。単一サーバーまたはロードバランシングされたプールからの通常のプロダクショントラフィックでは、これは目立ちません。1つのステージングキーを共有する分散CIジョブを実行している場合、フリート全体が同時にウォームアップする最初の時間に429エラーが発生する可能性があります。修正方法は、カナリアを1日1ノードから実行してから展開することです。

返金タイミングは実際には即時なので、即時のように感じられます

これはトラップというよりは嬉しい驚きです。SMS-Activateの返金は残高に反映されるのに数時間かかりましたが、VerifySMSの返金は60秒以内に反映されます。コストトラッカーがスケジュールで残高を読み取る場合、古いシステムでは見逃されていたであろうクレジットとして返金を登録します。照合ダッシュボードでは、初日に異常としてフラグが立てられることがあります。

パート4:価格比較の現実確認

シャットダウン前、SMS-Activateは市場の底値でした。ロシアの番号は検証あたり0.03ドルから0.05ドルで、大量購入者はさらに安く支払っていました。その底値はなくなりました。2026年4月現在、最も一般的なサービスに対する残りのプロバイダーの状況は、各プロバイダーの公開価格ページから取得したものです。時点:

サービス5simTextVerifiedSMSPVASMS-MANVerifySMS
WhatsApp / ロシア$0.014$0.05$0.035$0.10
WhatsApp / アメリカ$0.27$0.25$0.28$0.22$0.18
Telegram / ロシア$0.016$0.05$0.04$0.10
Telegram / アメリカ$0.35$0.40$0.38$0.30$0.20
Google / インドネシア$0.07$0.08$0.06$0.10

パターンは単純です。5simとSMS-MANは、ロシアの最低価格で優位に立ち、TextVerifiedは米国のプレミアムティアであり、VerifySMSは中間層に位置し、最も高価な米国の非VoIP番号を除いて、すべてにフラットな0.10ドルのベースラインを提供します。予算がSMS-Activateの底値価格に合わせて調整されていた場合、どの代替サービスを選択しても、検証あたり2倍から5倍多く支払うことを期待してください。

この表に関する2つの注意点。第一に、すべてのプロバイダー(VerifySMSを含む)は、キャリアコストに応じて個々の国の価格を上下させます。そのため、予算をコミットする前に、必ずダッシュボードで現在の価格を確認してください。第二に、成功した検証あたりの実効価格は、返金率に依存します。ステッカー価格が0.08ドルで成功率が70%のプロバイダーは、自動返金と成功率90%の0.10ドルのプロバイダーよりも、成功あたりのコストが高くなります。

パート5:10ステップの移行チェックリスト

これは、1月に私たち自身のユーザーが実際にたどったシーケンスです。リポジトリへのアクセス権を持つ単一の開発者、1つの本番サービス、およびステージング環境を想定しています。複数のサービスまたはモノレポを実行している場合は、カナリアの割合を増やしてください。

  1. すべての呼び出しサイトをインベントリ化します。 git grep -n 'sms-activate\.org\|handler_api\.php\|getNumber\|setStatus'を実行し、古いAPIにヒットするすべてのファイルを確認します。12を超えた場合は、まずラッパーモジュールを選択して呼び出しを集中化してから移行してください。
  2. VerifySMS APIキーを取得します。サインアップし、少額の残高を追加し、ステージング用のスコープ付きキーを生成します。本番キーはリポジトリに入れないでください。
  3. ベースURLを変更します。 SMS-Activateホストをapi.verifysms.app/compat/handler_api.phpに置き換えます。クエリ文字列は変更しないでください。差分がクリーンになるように、これを単独でコミットします。
  4. 既存のテストを実行します。テストが実際のAPIにヒットする場合は、ステージングに向け、形状の不一致がないか確認します。APIをモックしている場合は、ライブステージングエンドポイントに対しても実行し、契約のずれをキャッチします。
  5. 国IDを再確認します。コード内の国定数を確認します。レガシー数値IDを使用している場合、それらは引き続き機能します。機会があれば、ISO-3166コードに置き換えてください。次の開発者がそのファイルを触るときに感謝するでしょう。
  6. 返金請求を接続します。タイムアウトパスがsetStatusstatus=8で呼び出すことを確認します。これがないと、返金は引き続き行われます(リース期限切れの番号は自動返金されます)が、コストトラッカーは現実に追いつきません。
  7. コストトラッカーを更新します。価格表から解析するのではなく、X-VerifySMS-Costレスポンスヘッダーからコストを読み取ります。この単一の変更により、財務ダッシュボードがセント単位で正確になります。
  8. 監視。既存のベースラインに対して、成功率、p95レイテンシ、返金率のアラートを追加します。「大丈夫だろう」と思うしきい値ではなく、擁護できるしきい値を選択してください。
  9. カナリアを24時間5%で実行します。本番トラフィックの小さなスライスを新しいエンドポイントにルーティングします。アラートだけでなく、ダッシュボードも監視します。
  10. 残りを切り替えます。カナリアウィンドウがクリーンになったら、残りの95%を移動し、次のリリースサイクルまで古いクライアントコードをコメントアウトしたまま(削除しない)にして、迅速なロールバックを可能にします。

その次のリリースで古いコードを削除します。死んだ呼び出しサイトを1週間以上放置しないでください。次の人がモジュールに触れたときに、誤って新しいインテグレーションに再度貼り付けてしまう可能性があります。

よくある質問

SMS-Activate互換性レイヤーは実際のAPIですか、それともスタブですか?

これはapi.verifysms.app/compat/handler_api.phpにある実際のエンドポイントであり、SMS-Activateの公開ドキュメントの主要なアクションすべて(getBalancegetNumbergetStatussetStatusgetPricesgetCountries)を受け入れます。リクエストは内部でネイティブAPIにプロキシされるため、コードを変更せずにVerifySMSの価格設定、カバレッジ、および返金動作を利用できます。

古いAPIキーは機能しますか?

いいえ。SMS-Activate APIキーは、サービスがシャットダウンした日に認証を停止しました。VerifySMSから新しいキーが必要です。サインアップし、少額の残高を追加し、ダッシュボードからキーを生成してください。キーの形式は長さが同じなので、同じ環境変数に貼り付けることができます。

返金はSMS-Activateと比較してどのように機能しますか?

SMS-Activateでは、番号を使用しないとしてマークするために20分以内にステータスコード8でsetStatusを呼び出す必要があり、返金は数時間以内に手動で処理されました。VerifySMSは同じsetStatus呼び出しを受け入れ、60秒以内に残高に全額を返金します。setStatusをまったく呼び忘れた場合でも、リースウィンドウが期限切れになった後にSMSを受信しなかった番号はすべて自動的に返金されます。

どの国がサポートされていますか?

VerifySMSは200以上の国をカバーしています。SMS-Activateが提供していたすべての国がVerifySMSで利用可能であり、ロシア、インドネシア、ベトナム、ナイジェリア、米国も含まれます。既存の国IDマッピングを維持することも、いつでもISO-3166 alpha-2コードに移行することもできます。

価格は同じですか?

いいえ。ロシアの番号に対する検証あたり0.03ドルから0.05ドルのSMS-Activateの市場底値は、オープンマーケットからなくなりました。現在の市場価格は、一般的なサービスで0.10ドルから、より厳格なプラットフォームでの米国の非VoIP番号で0.25ドルまで様々です。VerifySMSはベースラインとして0.10ドルを請求し、ダッシュボードで国別の価格を公開しています。

ポーリングロジックを変更する必要がありますか?

いいえ。getStatus呼び出しは、SMS-Activateが使用していたのと同じ形式でSTATUS_WAIT_CODESTATUS_OKを返します。3〜5秒のポーリング間隔は引き続き機能します。新しい動作は、VerifySMSがダッシュボードでWebhook URLも公開していることだけです。これにより、イベント駆動フローを好む場合はポーリングを完全に停止できます。

互換性レイヤーが廃止された場合はどうなりますか?

互換性レイヤーは、永続的な公開インターフェイスと見なされます。その動作を変更する場合は、完全な移行ノートとともに最低6か月の廃止ウィンドウを公開します。ネイティブVerifySMS JSON APIも文書化されているため、都合の良いときにいつでも互換性レイヤーから移行できます。

お金を使わずにテストするにはどうすればよいですか?

VerifySMSダッシュボードはサンドボックスモードを公開しており、残高を debitせずにシミュレートされた電話番号と缶詰のSMSコードを返します。ダッシュボードでサンドボックスフラグを切り替えるか、リクエストにX-Sandbox-Modeヘッダーを送信して、本番稼働前にコードパスを実行します。

他のサービスからも移行できますか?

はい。このプレイブックはSMS-Activate APIを中心に書かれていますが、これはほとんどの stranded code が存在する場所だからです。しかし、同じチェックリストが5sim、SMS-MAN、またはその他のhandler_api互換サービスからの移行にも適用されます。互換性レイヤーは、以前に呼び出していたサービスに関係なく、handler_api.phpパラメータを認識します。

実際の移行にはどのくらい時間がかかりますか?

数 dozen の呼び出しサイトを持つ単一のサービスインテグレーションの場合、集中的な作業に2〜4時間、トラフィック全体を切り替える前に24時間のカナリアウィンドウを計画してください。カスタムエラー処理、分析、および再試行を備えた大規模なマルチサービス移行は、より長くかかる可能性がありますが、通常は1営業日以内に完了します。

履歴データは失われますか?

SMS-Activateの検証履歴は、サービスがシャットダウンしたときにオフラインになり、回復できません。VerifySMSは、アカウント上のすべての検証試行の完全な監査ログを12か月間保持しており、ダッシュボードおよび/compat/handler_api.php?action=getHistory拡張機能からアクセスできます。

これは私のGDPRまたはコンプライアンス姿勢に影響しますか?

VerifySMSは英国に登録されており、英国のGDPRに準拠しています。データ保持ポリシー、サブプロセッサー、およびDPAをプライバシーページに公開しています。以前のセットアップでSMS-ActivateとのDPAが必要だった場合は、お問い合わせください。1営業日以内に同じ契約に副署します。

次のステップ

ここまで読んだ方は、すでに必要なピースを持っています。インベントリステップから始め、ベースURLの交換をチームメイトにレビューしてもらい、カナリアを一晩実行してください。プレイブックは意図的に小さく作られています。難しいのは、カナリアウィンドウの後で、すべてを1つのコミットで切り替えるのをやめる規律です。

サイトの他の部分に関する関連資料:

移行を1晩で完了させませんか?

VerifySMS APIキーを作成する →

サンドボックスモードが含まれています · 自動返金保証 · 200以上の国 · SMS-Activate互換レイヤーは /compat/handler_api.php で利用可能

Next steps

If you have read this far, you already have the pieces you need. Start with the inventory step, get the base URL swap in front of a teammate for review, and run the canary overnight. The playbook is small on purpose; the hard part is the discipline to stop after the canary window instead of cutting everything over in one commit.

Related reading on the rest of the site:

Ready to cut the migration to a single evening?

Create a VerifySMS API key →

Sandbox mode included · Auto-refund guarantee · 200+ countries · SMS-Activate compat layer on /compat/handler_api.php