• Resolved Gerard Kanters

    (@gkanters)


    Hi Ben,

    Great work on this plugin. I do have an issue, otherwise I would not use the support ticket 🙂

    I also taken the liberty to sugges a fix. But this is just for your convenience.



    ## Problem

    The IPinfo module uses the ipinfo/ipinfo PHP library, which calls the **Legacy API** (https://ipinfo.io/{ip}) with a Bearer token. That Legacy endpoint has a **50,000 requests/month** limit for the free tier. Once that quota is exceeded, almost all requests return **429 Quota Exceeded** (e.g. 99% in weekly IPinfo reports).

    IPinfo’s **free unlimited** tier is the **Lite API**: https://api.ipinfo.io/lite/{ip}?token={token}.

    – Lite API docs: https://ipinfo.io/developers/lite-api  

    – Auth & URL structure: https://ipinfo.io/developers  

    – Free plan difference: https://ipinfo.io/faq/article/134-what-is-the-difference-between-using-the-authenticated-free-plan-and-just-the-public-api-with-no-account  

    **Expected:** Use the Lite API so that authenticated free-tier tokens get unlimited requests and no 429s from quota.



    ## Suggested fix

    Call the Lite API directly with wp_remote_get (and optional persistent cache with transients) instead of the vendor that uses the Legacy URL.

    ### Example implementation

    php<br><br><em>/**</em><br><br><em> * Get geolocation via IPinfo Lite API (unlimited for free tier).</em><br><br><em> * Do NOT use Legacy (ipinfo.io) – 50k/month limit → 429.</em><br><br><em> *</em><br><br><em> * @param string $ip    IP address to look up.</em><br><br><em> * @param string $token IPinfo access token.</em><br><br><em> * @return array|false Normalized location array or false.</em><br><br><em> */</em><br><br>public static function get_geolocation( $ip, $token ) {<br><br>    if ( empty( $token ) ) {<br><br>        return false;<br><br>    }<br><br>    <em>// Optional: persistent cache (e.g. transient) to reduce API calls.</em><br><br>    $cache_key = 'zerospam_ipinfo_' . md5( $ip );<br><br>    $cached    = get_transient( $cache_key );<br><br>    if ( false !== $cached ) {<br><br>        return $cached;<br><br>    }<br><br>    <em>// Correct: Lite API – unlimited for free accounts.</em><br><br>    $url = 'https://api.ipinfo.io/lite/' . rawurlencode( $ip ) . '?token=' . rawurlencode( $token );<br><br>    $response = wp_remote_get( $url, array( 'timeout' => 5 ) );<br><br>    if ( is_wp_error( $response ) ) {<br><br>        return false;<br><br>    }<br><br>    $code = wp_remote_retrieve_response_code( $response );<br><br>    if ( 429 === (int) $code || 200 !== (int) $code ) {<br><br>        return false;<br><br>    }<br><br>    $body = wp_remote_retrieve_body( $response );<br><br>    $data = json_decode( $body, true );<br><br>    if ( ! is_array( $data ) || empty( $data&#091;'country_code'] ) ) {<br><br>        return false;<br><br>    }<br><br>    <em>// Normalize to existing log_record format: country = 2-letter code, etc.</em><br><br>    $result = array(<br><br>        'country' => $data&#091;'country_code'],<br><br>        'region'  => isset( $data&#091;'region'] ) ? $data&#091;'region'] : '',<br><br>        'city'    => isset( $data&#091;'city'] ) ? $data&#091;'city'] : '',<br><br>        'postal'  => isset( $data&#091;'postal'] ) ? $data&#091;'postal'] : '',<br><br>    );<br><br>    if ( ! empty( $data&#091;'loc'] ) ) {<br><br>        $loc = explode( ',', $data&#091;'loc'], 2 );<br><br>        if ( 2 === count( $loc ) ) {<br><br>            $result&#091;'latitude']  = trim( $loc&#091;0] );<br><br>            $result&#091;'longitude'] = trim( $loc&#091;1] );<br><br>        }<br><br>    }<br><br>    <em>// Cache e.g. 14 days.</em><br><br>    set_transient( $cache_key, $result, 14 * DAY_IN_SECONDS );<br><br>    return $result;<br><br>}<br><br>



    ## API URLs reference

    | Use case   | Correct (Lite – unlimited)                          | Incorrect (Legacy – 50k/month)        |

    |———–|——————————————————|—————————————-|

    | Lookup IP | https://api.ipinfo.io/lite/8.8.8.8?token=TOKEN     | https://ipinfo.io/8.8.8.8 (+ Bearer) |

    | Auth      | ?token=TOKEN in query string (or Bearer on Lite)   | Bearer header on Legacy                |



    ## References

    – [IPinfo Lite API](https://ipinfo.io/developers/lite-api)

    – [IPinfo Developers – Authentication & URL structure](https://ipinfo.io/developers)

    – [Free plan vs public API](https://ipinfo.io/faq/article/134-what-is-the-difference-between-using-the-authenticated-free-plan-and-just-the-public-api-with-no-account)

Viewing 1 replies (of 1 total)
  • Plugin Author Ben Marshall

    (@bmarshall511)

    Thanks for the detailed report and for putting together a suggested implementation, super helpful.

    You’re exactly right: the current integration uses IPinfo’s Legacy endpoint (ipinfo.io/{ip}), which is capped at 50,000 requests/month on the free tier and can lead to widespread 429 “Quota Exceeded” responses once that limit is hit.

    We’ve updated the IPinfo module to use the Lite API endpoint instead (api.ipinfo.io/lite/{ip}?token=…), which is the unlimited option for authenticated free-tier tokens. We also added persistent caching to reduce API calls further, and removed the vendor dependency in favor of the native WordPress HTTP API.

    This change will be included in v5.7.2.

    Also, if you have a minute, we’d really appreciate a quick review on WordPress.org, it helps a ton with visibility and lets other site owners know the plugin is actively maintained.

    Thanks again, we’re always here to help. Just reply if you have any questions.

Viewing 1 replies (of 1 total)

You must be logged in to reply to this topic.