Quick start
An SMS Kit is the service you activate and manage. Its associated phone number identifies it in phone-based API requests.
Copy your token from API Settings. Send POST requests to https://www.groovl.com/api.php with JSON and a Bearer token. API access is available to all active accounts.
curl --request POST 'https://www.groovl.com/api.php' \
--header 'Authorization: Bearer YOUR_API_TOKEN' \
--header 'Content-Type: application/json' \
--data '{"resource":"balance"}' {
"resource": "balance",
"resolution": "ok",
"balance": "100.0000"
}Authentication
Authorization: Bearer YOUR_API_TOKENThe token identifies your account and is case-sensitive. Do not include user, token, or interface in the request body.
Replacing your API token invalidates the old one immediately. Your webhook signing secret is separate and does not change when you replace your API token.
Request and response rules
- Use POST with
Content-Type: application/json. Send one JSON object, up to 65,536 bytes. - Include
resource. Unknown fields are rejected. Use strings as shown in the examples. - Use digits only for final phone numbers and uppercase two- or three-letter country codes.
- Check
resolution, even with HTTP 200:ok,pending, orfailed. Acceptance does not confirm completion of activation or cancellation. - Amounts are in Credits. Dates use
YYYY-MM-DD HH:MM:SSwithout a timezone offset; do not assume UTC.
Resources
| Resource | Required | Optional | Behavior |
|---|---|---|---|
balance | None | None | Read |
countries | None | None | Read |
search | country | phone | Read |
plans | country | None | Read |
activate | pid | plan | Completes after acceptance |
list | None | phone, status, page, autorenew, country | Read |
settings | phone | None | Read |
update | phone, name, to, address | None | Immediate |
autorenew | phone, autorenew | None | Immediate |
plan | phone, plan | None | Next renewal |
cancel | phone | None | Completes after acceptance |
balance
Get your account balance in Credits. Credits committed to pending requests may still appear in this balance but are unavailable to spend.
Request
{
"resource": "balance"
}Response
{
"resource": "balance",
"resolution": "ok",
"balance": "100.0000"
}countries
List countries with available SMS Kits, ordered by country name. Requires a Bearer token; no additional parameters or pagination. Each country appears once. Names and telephone prefixes come from the country directory; availability follows the same inventory rules as search.
Request
{
"resource": "countries"
}Response
{
"resource": "countries",
"resolution": "ok",
"response": [
{
"country2x": "FR",
"title": "France",
"prefix": "33"
},
{
"country2x": "GB",
"title": "United Kingdom",
"prefix": "44"
}
]
}country2x is the two- or three-letter country code to send as country in search and plans. prefix is a string without the leading +. If no countries have available SMS Kits, the response is resolution="ok" with response=[]. Availability can change before activation; displaying a country or number does not reserve it.
search
Find available SMS Kits and their associated phone numbers. Required: country. Optional: phone prefix with * wildcard. Returns up to 10 results; no pagination. Use pid to activate a result.
Request
{
"resource": "search",
"country": "FR",
"phone": "33*"
}Response
{
"resource": "search",
"resolution": "ok",
"response": [
{
"pid": "NUMBER_ID",
"phone": "DISPLAY_NUMBER",
"carrier": "CARRIER",
"country": "FR"
}
]
}plans
List plans for the required country, ordered by renewal fee. premium="1" requires a Premium account. Account-specific pricing may apply.
Request
{
"resource": "plans",
"country": "FR"
}Response
{
"resource": "plans",
"resolution": "ok",
"response": [
{
"plan": "PLAN_ID",
"country": "FR",
"title": "PLAN_TITLE",
"days": "30",
"setup": "0.0000",
"renew": "159.0000",
"premium": "0",
"inbound_sms": "PLAN_VALUE",
"outbound_sms": "n/a"
}
]
}activate
Activate a search result. Required: pid. Optional: plan; defaults to the lowest-renewal-fee eligible non-Premium plan for the country. Available Credits must cover setup and the first renewal.
Request
{
"resource": "activate",
"pid": "NUMBER_ID",
"plan": "1"
}Response
{
"resource": "activate",
"resolution": "ok",
"pid": "NUMBER_ID",
"plan": "1",
"phone": "33600000000",
"terms": "PLAN_TERMS",
"response": "Request has been sent and will be done in a couple of minutes"
}Save pid before sending the request. Confirm completion through the activation webhook. For pending, see delayed activation.
Delayed activation
Some carriers need additional setup. A pending response includes a masked number and the original pid. Keep the request open; do not resubmit it or use the masked number in phone-based operations.
Pending response
{
"resource": "activate",
"resolution": "pending",
"pid": "NUMBER_ID",
"plan": "1",
"phone": "(+1) 202-55X-XXXX",
"terms": "PLAN_TERMS",
"response": "Your request is in and may take up to 24 hours to complete, but we'll work to get it done sooner"
}Completion webhook
{
"user": "PUBLIC_ACCOUNT_ID",
"action": "plan setup",
"resolution": "done",
"phone": "12025550123",
"date": "2026-10-15 12:00:00",
"pid": "NUMBER_ID"
}Verify the signature, match pid to your saved request, and replace the masked value with the final phone. Set up your webhook before requesting activation.
list
List your SMS Kits and associated phone numbers. Returns up to 10 per page, ordered by expiration date.
| Optional field | Values |
|---|---|
status | 1 Allocation, 2 Live (default), 3 Released, 4 Storage |
page | Starts at 1 |
phone | Prefix; digits and * wildcard |
country | Two- or three-letter country code |
autorenew | 0 off, 1 on |
Request
{
"resource": "list",
"status": "2",
"page": "1"
}Response
{
"resource": "list",
"resolution": "ok",
"pages": "1",
"page": "1",
"response": [
{
"phone": "33600000000",
"status": "2",
"country": "FR",
"expires": "2026-11-15 12:00:00",
"autorenew": "1",
"plan": "1"
}
]
}No matches return failed with Nothing to display yet.
settings
Read an active SMS Kit’s settings using its phone number. Required: phone. The SMS Kit must belong to your account.
Request
{
"resource": "settings",
"phone": "33600000000"
}Response
{
"resource": "settings",
"resolution": "ok",
"phone": "33600000000",
"country": "FR",
"name": "My SMS Kit",
"expires": "2026-11-15 12:00:00",
"plan": "1",
"fee": "159.0000",
"autorenew": "1",
"to": "0",
"address": ""
}update
Update an active SMS Kit’s label and SMS forwarding settings. All fields are required: phone, name (up to 255 bytes; may be empty), to, and address (up to 2,048 bytes).
| to | address |
|---|---|
0 | Empty string; turns forwarding off |
1 | Email address |
2 | Public HTTPS callback URL on port 443 |
Request
{
"resource": "update",
"phone": "33600000000",
"name": "My SMS Kit",
"to": "0",
"address": ""
}Response
{
"resource": "update",
"resolution": "ok",
"phone": "33600000000",
"response": "Updated"
}See SMS forwarding for delivery details.
SMS forwarding to a URL
Use update to set a forwarding URL for each active SMS Kit. Set to to "2", and include the SMS Kit’s current name to keep its label.
Request
{
"resource": "update",
"phone": "33600000000",
"name": "My SMS Kit",
"to": "2",
"address": "https://your-site.com/sms"
}Response
{
"resource": "update",
"resolution": "ok",
"phone": "33600000000",
"response": "Updated"
}Use settings to check the saved forwarding configuration. To disable forwarding, send update with to="0" and address="".
Use a public HTTPS hostname on port 443 with a valid certificate and IPv4 support. Redirects are not followed. This URL receives SMS; your account Webhook URL receives SMS Kit events.
Incoming SMS
We POST JSON to your URL with Content-Type: application/json:
{
"to": "33600000000",
"from": "12025550123",
"text": "Your verification code is 123456.",
"id": "4d139a85327e09f9aafc5183b328156a"
}All four fields are strings. to is your number, from is the sender’s number or alphanumeric name, text is the message, and id identifies the SMS.
Verify the signature with your account’s webhook signing secret from API Settings. Use X-Proovl-Event-Id for signature verification and the JSON id to recognize duplicate SMS. These are different IDs. No API token is sent.
SMS callbacks contain text messages only. Non-text messages, such as binary carrier settings, are not forwarded.
After verification, save the message and return 204 No Content (or another 2xx status) within 10 seconds. Return 2xx for an SMS you already saved, without processing it again. We ignore the response body.
SMS forwarding runs from a queue, with up to six attempts. Timeouts and non-2xx responses are retried after approximately 1, 5, 15, 60, and 360 minutes. Retry-After on HTTP 429 or 503 can extend the delay. Retries keep the same SMS and event IDs; return 2xx for an SMS already saved. Each attempt uses your current forwarding URL and signing secret. Disabling forwarding or deactivating the SMS Kit or account stops queued delivery when checked by the worker. Your SMS remains available in your account if forwarding fails.
autorenew
Set auto-renew for your active SMS Kit. Required: phone, autorenew (1 on, 0 off). Takes effect immediately without charging or renewing the SMS Kit.
Request
{
"resource": "autorenew",
"phone": "33600000000",
"autorenew": "1"
}Response
{
"resource": "autorenew",
"resolution": "ok",
"phone": "33600000000",
"autorenew": "1",
"response": "Auto renew ON"
}An unchanged setting returns failed with Nothing to update.
plan
Choose the plan for your active SMS Kit’s next renewal. Required: phone, plan. Country, account eligibility, and available Credits must match the new plan. No immediate charge.
Request
{
"resource": "plan",
"phone": "33600000000",
"plan": "1"
}Response
{
"resource": "plan",
"resolution": "ok",
"phone": "33600000000",
"plan": "1",
"response": "PLAN_TITLE will start after 2026-11-15 12:00:00"
}Choosing the current plan returns Nothing to update.
cancel
Cancel your active SMS Kit using its phone number. Required: phone. This ends the service, rather than just turning off auto-renew. Confirm completion through a webhook or SMS Kit status.
Request
{
"resource": "cancel",
"phone": "33600000000"
}Response
{
"resource": "cancel",
"resolution": "ok",
"phone": "33600000000",
"response": "Request has been sent"
}A pending cancellation returns failed with Request already sent.
Errors and retries
| HTTP | Action |
|---|---|
| 200 | Check resolution; the operation may have failed. |
| 400 | Check the JSON, resource, and fields. |
| 401 | Check your token and account status. |
| 405 | Use POST. |
| 413 | Keep the body within 65,536 bytes. |
| 415 | Use Content-Type: application/json. |
| 429 | Wait for Retry-After. |
| 500 / 503 | Check operation status before retrying a write. Follow Retry-After when present. |
There is no general idempotency-key parameter. After a timeout, check the SMS Kit’s status and webhooks before repeating a request.
| response | Meaning |
|---|---|
Number not found | Not owned by you or unavailable for this operation. |
Number not available | Cannot be activated. |
Insufficient funds, please refill your balance | Not enough available Credits. |
Nothing to update | The setting already matches. |
Request already sent | A matching request is still pending. |
Webhooks
Set your Webhook URL in API Settings. Use HTTPS on port 443 with a hostname that resolves to a public IPv4 address. Redirects, IP-only URLs, private addresses, and URL credentials are unsupported. An empty URL disables delivery.
| action | Event |
|---|---|
plan setup | Activation |
plan auto renew | Auto-renewal |
plan cancelation | Cancellation |
{
"user": "PUBLIC_ACCOUNT_ID",
"action": "plan auto renew",
"resolution": "done",
"phone": "33600000000",
"date": "2026-10-15 12:00:00"
}resolution is done or failed. user identifies the account. date is the recorded action timestamp, not the delivery time or a guaranteed completion time. Final activation and cancellation events include pid.
Accepting events
- Verify the signature before processing.
- Save the event and return HTTP 2xx within 10 seconds.
- Deduplicate by
X-Proovl-Event-Id. Return 2xx for an already processed event without repeating its effects. - Failed delivery is retried up to six total attempts. Delays after attempts 1–5 are approximately 1, 5, 15, 60, and 360 minutes. HTTP 429 or 503 with
Retry-Aftercan extend the delay.
Replacing the signing secret
Use Replace signing secret in API Settings, then update your receiver. New attempts, including retries, use the new secret; an in-flight attempt may still use the old one. Your API token is unchanged.
Verify webhook signatures
The same verification applies to SMS callbacks and action webhooks.
| Header | Value |
|---|---|
X-Proovl-Event-Id | Event ID; unchanged across retries |
X-Proovl-Timestamp | Unix seconds for this delivery attempt |
X-Proovl-Signature | v1= + lowercase HMAC-SHA256 hex |
Calculate HMAC-SHA256 over timestamp + "." + eventId + "." + rawBody. Use the displayed signing secret as the literal key; do not hex-decode it or re-encode the JSON.
PHP example: load $secret from your private configuration.
$body = file_get_contents('php://input');
$event = $_SERVER['HTTP_X_PROOVL_EVENT_ID'] ?? '';
$timestamp = $_SERVER['HTTP_X_PROOVL_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_PROOVL_SIGNATURE'] ?? '';
if (!is_string($secret) || !preg_match('/^[a-f0-9]{64}$/D', $secret)
|| !preg_match('/^[1-9][0-9]{0,19}$/D', $event)
|| !preg_match('/^[0-9]{10}$/D', $timestamp)
|| !preg_match('/^v1=[a-f0-9]{64}$/D', $signature)
|| abs(time() - (int)$timestamp) > 300
|| !hash_equals('v1=' . hash_hmac('sha256', $timestamp . '.' . $event . '.' . $body, $secret), $signature)) {
http_response_code(401);
exit;
}
// Only now decode and validate JSON, then process the event.
Keep your server clock synchronized. After verification, validate the JSON and save it before returning 2xx. Deduplicate SMS by the JSON id and SMS Kit events by X-Proovl-Event-Id, keeping those keys separate. SMS Kit event retries keep the event ID but use a new timestamp and signature.
Moving from the previous API
- Use Bearer authentication. Remove
user,token, andinterfacefrom request JSON. - Check both HTTP status and
resolution. - Verify SMS callbacks and SMS Kit event webhooks with your webhook signing secret, not your API token.
- Save activation
pidvalues and deduplicate webhook events.