Official Python SDK for EngageLab OTP.
Zero dependencies. Python 3.8+.
pip install engagelab-otpimport os
from engagelab_otp import OTPClient
otp = OTPClient(
os.environ["ENGAGELAB_DEV_KEY"],
os.environ["ENGAGELAB_DEV_SECRET"],
)
# Platform-generated OTP — easiest path
result = otp.send("+6591234567", "your-template-id", {"name": "Alice"}, language="en")
check = otp.verify(result["message_id"], user_typed_code)
if check["verified"]:
...| Mode | Method | When to use |
|---|---|---|
| Platform-generated | otp.send() |
EngageLab generates and stores the code. You only call verify() later. |
| Caller-generated | otp.send_custom() |
You generate the code yourself. EngageLab is just the carrier. |
# Platform-generated
r = otp.send("+6591234567", "tpl-id", {"name": "Alice"}, language="en")
v = otp.verify(r["message_id"], "123456")
# Caller-generated
otp.send_custom("+6591234567", "custom-tpl", {"code": "482910", "name": "Alice"})EngageLab signs callbacks with X-CALLBACK-ID (HMAC-SHA256). The SDK verifies signatures and parses events for you.
Whitelist source IPs in your firewall:
119.8.170.74,114.119.180.30
from flask import Flask
from engagelab_otp import WebhookVerifier, MessageStatusEvent
app = Flask(__name__)
verifier = WebhookVerifier(
username=os.environ["ENGAGELAB_WEBHOOK_USERNAME"],
secret=os.environ["ENGAGELAB_WEBHOOK_SECRET"],
)
def handle(events):
for e in events:
if not isinstance(e, MessageStatusEvent):
continue
if not e.is_terminal:
continue # mid-flight, wait
if e.status == "delivered":
mark_delivered(e.message_id)
elif e.status == "verified":
mark_verified(e.message_id)
app.add_url_rule(
"/webhook",
"engagelab_webhook",
verifier.flask_view(handle),
methods=["POST"],
)parse_events() returns instances of four dataclasses:
| Class | When | Key fields |
|---|---|---|
MessageStatusEvent |
per-message lifecycle | message_id, status, is_terminal, current_send_channel, error_code |
NotificationEvent |
account-level alert | event, data |
UplinkEvent |
inbound user reply | data["from"], data["body"] |
SystemEvent |
console action audit | event, data |
plan · target_valid · target_invalid
sent · sent_failed
delivered · delivered_failed
verified · verified_failed · verified_timeout
is_terminal == True for: delivered, delivered_failed, sent_failed, verified*, target_invalid.
from engagelab_otp import EngagelabError
try:
otp.send("+6591234567", "tpl", {})
except EngagelabError as e:
if e.retryable:
# HTTP 429/5xx, or API codes 1000/5001/5016
# → exponential backoff
...
else:
# Permanent failure — fix call or notify user
print(e.code, e.http_status, str(e))See examples/ for runnable code:
01_send_and_verify.py— platform-generated OTP, full flow02_send_custom.py— caller-generated code, single + bulk03_webhook_flask.py— receive callbacks via Flask04_error_handling.py— retry strategy and error categorization
python tests/test_sdk.pyMIT