Each SMS message can be linked to a specific webhook configuration to ensure notifications are sent to the designated endpoint.
Example in the send methods:
{
"webhook": {
"url": "https://example.com/callback",
"authentication": {
"type": "NONE"
}
}
}The webhook settings include the following parameters.
- url: The destination URL where webhook notifications will be sent.
- Must be a valid, publicly accessible URL
- HTTPS is required
- authentication: The authentication method used when calling your endpoint.
- NONE: No authentication is required. The webhook request will be sent without any credentials.
- JWS: Namirial signs each outgoing callback with an RSA private key (RS256) and attaches the signature as an
X-JWS-Signatureheader. This allows you to verify the authenticity and integrity of every notification. See Verify JWS signatures below.
The webhook configuration is defined per message, inline in the send request. There is no global reusable configuration.
For details on notification payloads, retry behavior, and status transitions, see Understanding webhook.
When type is JWS, Namirial signs each callback and attaches a compact JWS token (RFC 7515, RS256) to the x-jws-signature request header. The request body is sent in plain text — JWS guarantees authenticity and integrity.
The x-jws-signature header contains a compact JWS token with three base64url-encoded parts separated by .:
x-jws-signature: <header>.<payload>.<signature>Example:
eyJraWQiOiIwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDAiLCJhbGciOiJSUzI1NiJ9
.
eyJhdWQiOiJodHRwczovL2V4YW1wbGUuY29tL2NhbGxiYWNrIiwic3ViIjoiMDAwMDAwMDAtMDAwMC0wMDAwLTAwMDAtMDAwMDAwMDAwMDAwIiwiYm9keV9zaGEyNTYiOiJBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUE9IiwiaXNzIjoiaHR0cHM6Ly9zbXMtYXBpc3RhdHVzLm5hbWlyaWFsdHNwLmNvbS9hcGkiLCJleHAiOjE3MDAwMDAwMDAsImlhdCI6MTcwMDAwMDAwMCwianRpIjoiMDAwMDAwMDAtMDAwMC0wMDAwLTAwMDAtMDAwMDAwMDAwMDAwIn0
.
SIGNATUREDecoded:
- Header:
{"kid":"00000000-0000-0000-0000-000000000000","alg":"RS256"} - Payload:
{"aud":"https://example.com/callback","sub":"00000000-0000-0000-0000-000000000000","body_sha256":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=","iss":"https://sms-apistatus.namirialtsp.com/api","exp":1700000000,"iat":1700000000,"jti":"00000000-0000-0000-0000-000000000000"} - Signature: base64url-encoded RSA signature
To verify a callback, follow these steps.
Step 1 — Retrieve the key ID
Decode the JWS header (the first part of the token, before the first .) from base64url to JSON.
{
"alg": "RS256",
"kid": "00000000-0000-0000-0000-000000000000"
}alg: the signing algorithm — alwaysRS256(RSASSA-PKCS1-v1_5 using SHA-256, as described in RFC7518 §3.1)kid: the key ID — identifies which public key was used to sign the request
Step 2 — Retrieve the public key
Fetch the JWKS endpoint and find the key matching the kid from step 1:
GET https://sms-apistatus.namirialtsp.com/api/.well-known/jwks.jsonStep 3 — Inspect the payload
The signed input is the JWS token without the signature — take the x-jws-signature value and remove everything after the last . (the signature part). What remains, <header>.<payload>, is the exact string that was signed.
The payload, once base64url-decoded, contains the following claims:
{
"iss": "https://sms-apistatus.namirialtsp.com/api",
"aud": "https://example.com/callback",
"sub": "00000000-0000-0000-0000-000000000000",
"iat": 1700000000,
"exp": 1700000300,
"jti": "00000000-0000-0000-0000-000000000001",
"body_sha256": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
}| Claim | Description |
|---|---|
iss | Issuer — identifies Namirial (e.g. https://sms-apistatus.namirialtsp.com/api) |
aud | Audience — your webhook URL |
sub | Subject — the messageId of the SMS |
iat | Issued-at timestamp (Unix) |
exp | Expiry timestamp — token is valid for 5 minutes (iat + 300) |
jti | Unique token ID — use for replay protection |
body_sha256 | Base64url-encoded SHA-256 hash of the raw request body |
Step 4 — Verify the signature
Using the algorithm specified in alg (step 1) and the public key retrieved in step 2, verify that the signature (the third part of the x-jws-signature token) is a valid signature of the string <header>.<payload> — i.e. the token with the signature part removed.
If the verification fails, the callback must be rejected.
token = request.headers["x-jws-signature"]
parts = token.split(".")
header = base64url_decode(parts[0])
payload = base64url_decode(parts[1])
signature = base64url_decode(parts[2])
signed_input = parts[0] + "." + parts[1]
public_key = jwks_get_key(header["kid"])
assert rsa_sha256_verify(public_key, signed_input, signature)The JWS payload includes a body_sha256 claim containing the base64url-encoded SHA-256 hash of the raw request body. After verifying the signature, compute the SHA-256 hash of the received body and compare it with body_sha256 to confirm the payload has not been altered.
x-jws-signature header:
eyJraWQiOiIwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDAiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiJodHRwczovL2V4YW1wbGUuY29tL2NhbGxiYWNrIiwic3ViIjoiMDAwMDAwMDAtMDAwMC0wMDAwLTAwMDAtMDAwMDAwMDAwMDAwIiwiYm9keV9zaGEyNTYiOiJBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUE9IiwiaXNzIjoiaHR0cHM6Ly9zbXMtYXBpc3RhdHVzLm5hbWlyaWFsdHNwLmNvbS9hcGkiLCJleHAiOjE3MDAwMDAwMDAsImlhdCI6MTcwMDAwMDAwMCwianRpIjoiMDAwMDAwMDAtMDAwMC0wMDAwLTAwMDAtMDAwMDAwMDAwMDAwIn0.SIGNATURERequest body:
{
"id": "00000000-0000-0000-0000-000000000001",
"webhookConfigurationId": "00000000-0000-0000-0000-000000000002",
"type": "STATUS_UPDATE",
"createdAt": "2024-01-01T00:00:00Z",
"message": {
"id": "00000000-0000-0000-0000-000000000000",
"reference": {},
"channel": "SMS",
"status": "DELIVERED",
"providerAcceptanceAt": "2024-01-01T00:00:00Z",
"statusChangedAt": "2024-01-01T00:00:01Z",
"provider": {
"name": "PROVIDER1",
"id": "00000000-0000-0000-0000-000000000003",
"status": "DELIVERED",
"code": "0"
},
"sms": { "segments": 1 }
}
}