Skip to content
Start free

PHP, Python, and API clients

Wabery’s REST API works from any HTTP client. The TypeScript SDK is optional: PHP and Python applications use the same endpoints, JSON bodies, authentication, idempotency keys, and response objects.

Every server-side REST client uses:

Base URL: https://api.wabery.com/v1
Authorization: Bearer <WABERY_API_KEY>
Content-Type: application/json

Keep the secret key in an environment variable:

Terminal window
export WABERY_API_KEY="wab_live_..."

Wabery publishes a standard OpenAPI 3.1 description containing every public operation, request schema, authentication requirement, and response schema:

OpenAPI URL
https://api.wabery.com/v1/openapi.json

In any OpenAPI-compatible client:

  1. Choose Import, OpenAPI, or Import from URL.
  2. Paste the URL above.
  3. Select https://api.wabery.com/v1 as the server if prompted.
  4. Configure Bearer authentication with your WABERY_API_KEY.
  5. Choose an operation, replace placeholder resource IDs, and send the request.

This works with clients that import OpenAPI 3.x from a URL, including local, offline, open-source, and hosted options. Wabery does not require or endorse a particular API client.

PHP 8.1 or newer with the cURL extension is sufficient; no Wabery package is required. Put this reusable helper in your server application:

wabery.php
<?php
/**
* @return array<string, mixed>|list<mixed>|null
*/
function waberyRequest(
string $method,
string $path,
?array $body = null,
): ?array {
$apiKey = getenv("WABERY_API_KEY");
if ($apiKey === false || $apiKey === "") {
throw new RuntimeException("WABERY_API_KEY is not set");
}
$url = "https://api.wabery.com/v1/" . ltrim($path, "/");
$handle = curl_init($url);
if ($handle === false) {
throw new RuntimeException("Could not initialize cURL");
}
$headers = [
"Accept: application/json",
"Authorization: Bearer " . $apiKey,
];
if ($body !== null) {
$headers[] = "Content-Type: application/json";
}
$options = [
CURLOPT_CUSTOMREQUEST => strtoupper($method),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => $headers,
];
if ($body !== null) {
$options[CURLOPT_POSTFIELDS] = json_encode(
$body,
JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES,
);
}
if (!curl_setopt_array($handle, $options)) {
throw new RuntimeException("Could not configure the Wabery request");
}
$responseBody = curl_exec($handle);
if ($responseBody === false) {
throw new RuntimeException("Wabery request failed: " . curl_error($handle));
}
$status = curl_getinfo($handle, CURLINFO_RESPONSE_CODE);
if ($status < 200 || $status >= 300) {
throw new RuntimeException(
"Wabery API returned HTTP {$status}: {$responseBody}",
);
}
if ($responseBody === "") {
return null;
}
return json_decode($responseBody, true, 512, JSON_THROW_ON_ERROR);
}

Send a text reply:

send-text.php
<?php
require __DIR__ . "/wabery.php";
$message = waberyRequest("POST", "/messages", [
"channel_id" => "channel_...",
"conversation_id" => "conversation_...",
"text" => "Thanks for your message",
]);
echo $message["id"] . PHP_EOL;

Send an image from a public HTTPS URL:

send-image.php
<?php
require __DIR__ . "/wabery.php";
$message = waberyRequest("POST", "/messages", [
"channel_id" => "channel_...",
"conversation_id" => "conversation_...",
"idempotency_key" => "reply-image-123",
"media" => [
"type" => "image",
"link" => "https://cdn.example.com/photo.jpg",
],
]);

Read a conversation’s messages:

list-messages.php
<?php
require __DIR__ . "/wabery.php";
$result = waberyRequest(
"GET",
"/conversations/conversation_.../messages?order=asc&limit=100",
);
foreach ($result["data"] as $message) {
echo ($message["content"] ?? "[media]") . PHP_EOL;
}

Verify the signature against the exact raw request body before decoding JSON:

webhook.php
<?php
$rawBody = file_get_contents("php://input");
$signature = $_SERVER["HTTP_X_WABERY_SIGNATURE"] ?? "";
$secret = getenv("WABERY_WEBHOOK_SECRET");
if ($rawBody === false || $secret === false || $secret === "") {
http_response_code(500);
exit;
}
$expected = "sha256=" . hash_hmac("sha256", $rawBody, $secret);
if (!hash_equals($expected, $signature)) {
http_response_code(401);
exit;
}
$event = json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR);
// Queue or process $event, then acknowledge promptly.
http_response_code(204);

Install the Requests package:

Terminal window
python -m pip install requests

Create one reusable client:

wabery.py
import os
from typing import Any
import requests
BASE_URL = "https://api.wabery.com/v1"
API_KEY = os.environ["WABERY_API_KEY"]
session = requests.Session()
session.headers.update(
{
"Accept": "application/json",
"Authorization": f"Bearer {API_KEY}",
}
)
def wabery_request(
method: str,
path: str,
*,
json: dict[str, Any] | None = None,
params: dict[str, Any] | None = None,
) -> Any:
response = session.request(
method,
f"{BASE_URL}/{path.lstrip('/')}",
json=json,
params=params,
timeout=(10, 30),
)
try:
response.raise_for_status()
except requests.HTTPError as error:
raise RuntimeError(
f"Wabery API returned HTTP {response.status_code}: {response.text}"
) from error
return response.json() if response.content else None

Send a text reply:

send_text.py
from wabery import wabery_request
message = wabery_request(
"POST",
"/messages",
json={
"channel_id": "channel_...",
"conversation_id": "conversation_...",
"text": "Thanks for your message",
},
)
print(message["id"])

Send an image from a public HTTPS URL:

send_image.py
from wabery import wabery_request
message = wabery_request(
"POST",
"/messages",
json={
"channel_id": "channel_...",
"conversation_id": "conversation_...",
"idempotency_key": "reply-image-123",
"media": {
"type": "image",
"link": "https://cdn.example.com/photo.jpg",
},
},
)

Read a conversation’s messages:

list_messages.py
from wabery import wabery_request
result = wabery_request(
"GET",
"/conversations/conversation_.../messages",
params={"order": "asc", "limit": 100},
)
for message in result["data"]:
print(message.get("content") or "[media]")

Pass the exact raw bytes supplied by your web framework:

webhook.py
import hashlib
import hmac
def verify_wabery_signature(
raw_body: bytes,
signature: str,
secret: str,
) -> bool:
digest = hmac.new(
secret.encode("utf-8"),
raw_body,
hashlib.sha256,
).hexdigest()
expected = f"sha256={digest}"
return hmac.compare_digest(expected, signature)

Only parse the event after verify_wabery_signature(...) returns True. See Webhooks and events for payloads, retries, and media downloads.

For REST calls, the body shown in documentation is ordinary JSON:

POST /v1/messages request body
{
"channel_id": "channel_...",
"conversation_id": "conversation_...",
"text": "Thanks for your message"
}

The surrounding PHP, Python, cURL, or graphical API client supplies the HTTP method, URL, Bearer authentication, and Content-Type. JSON by itself is not a complete API request.

  • Successful sends return 202 Accepted; delivery remains asynchronous.
  • Treat 429 and transient 5xx responses as retryable with backoff.
  • Supply a stable Idempotency-Key header or idempotency_key for retried message sends.
  • Do not retry validation or authentication failures without changing the request.
  • Use message status webhooks or GET /v1/messages/{message_id} to observe final delivery.