Webhooks¶
1sms.az can call your server when a message changes state. Each call is signed, so you can confirm it really came from 1sms.az before acting on it.
How the signature works¶
Every webhook arrives with two headers:
X-1sms-Signature, an HMAC-SHA256 of the request, as hex.X-1sms-Timestamp, the unix time the request was signed.
The signature is computed over the timestamp and the raw request body, keyed with your
API secret. verify_signature recreates it and compares the two in constant time. It
also rejects a timestamp more than five minutes old, so an old call cannot be replayed.
Verify a call¶
Always verify against the raw request body, before any JSON parsing reformats it.
from onesms import WebhookEvent, verify_signature
signature = request.headers["X-1sms-Signature"]
timestamp = request.headers["X-1sms-Timestamp"]
raw_body = request.get_data()
if not verify_signature("your_api_secret", timestamp, raw_body, signature):
raise ValueError("Invalid webhook signature")
event = WebhookEvent.from_payload(request.get_json())
if event.is_final:
print(event.message_id, event.status_text)
WebhookEvent reads the delivery fields out of the payload and gives you the same
status and is_final helpers used elsewhere in the library.
Adjust the time window¶
The default tolerance is five minutes. Widen it with tolerance_seconds if your server
clock drifts or you process calls from a queue:
Flask example¶
from flask import Flask, request
from onesms import WebhookEvent, verify_signature
app = Flask(__name__)
SECRET = "your_api_secret"
@app.post("/webhooks/1sms")
def receive():
signature = request.headers.get("X-1sms-Signature", "")
timestamp = request.headers.get("X-1sms-Timestamp", "")
if not verify_signature(SECRET, timestamp, request.get_data(), signature):
return "", 400
event = WebhookEvent.from_payload(request.get_json())
print(event.message_id, event.status_text)
return "", 204