Listen to webhooks
Montonio uses webhooks to notify your application about events in real time, such as when a payment is completed or a refund is processed. This guide covers how Montonio webhooks work, how to verify them, and how to handle them correctly.
Overview
When certain events occur β like a customer completing a payment β Montonio sends an HTTP POST request to a URL you specify (the notificationUrl). This ensures your system is updated even if the customer closes their browser before being redirected back to your store.
Montonio sends two types of webhooks to the same notificationUrl:
- Order webhooks β Sent when the payment status of an order changes (e.g. payment completed). The request body contains an
orderTokenJWT. See the Orders guide, Payment links guide, and Embedded Cards guide for details. - Refund webhooks β Sent when the status of a refund changes (e.g. refund successful or rejected). The request body contains a
refundTokenJWT. See the Refunds guide for details.
Both token types are JWTs signed with your Secret Key using HS256 and are verified the same way.
How webhooks work
- When creating an order or payment link, you include a
notificationUrlin the JWT payload. - When an event occurs (e.g. a payment is completed or a refund status changes), Montonio sends an HTTP
POSTrequest to that URL. - The request body contains a signed JWT β either
orderTokenorrefundTokenβ with the event details. - Your server verifies the JWT signature and processes the event.
Webhook source identification
IP addresses
All Montonio webhooks are sent from the following IP addresses:
35.156.245.4235.156.159.169
β οΈ Allowlist these IP addresses in your firewall or WAF to ensure you receive webhook notifications. If you are using Cloudflare, see our guide on configuring Cloudflare to allow Montonio webhooks.
User-Agent
Montonio webhook requests are sent with the following User-Agent header:
MontonioWebhooks/1.0Webhook payload
Montonio webhooks deliver their payload as a JSON object in the POST body. The field name indicates the webhook type:
Order webhook:
{ "orderToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}Refund webhook:
{ "refundToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}Both tokens are JWTs signed with your Secret Key. The specific fields in each token depend on the webhook type β refer to the Orders guide and Refunds guide for the decoded token contents.
Verifying webhooks
You must verify the JWT signature using your Secret Key to ensure the webhook is authentic and has not been tampered with. Never trust webhook data without verification. Both orderToken and refundToken are verified the same way.
/** * We recommend using the jsonwebtoken package to verify * Json Web Tokens. You can install it with npm: * > npm install jsonwebtoken * More information can be found at * https://www.npmjs.com/package/jsonwebtoken */import jwt from 'jsonwebtoken';
// Determine the webhook type from the POST bodyconst { orderToken, refundToken } = req.body;const token = orderToken || refundToken;
try { const decoded = jwt.verify(token, 'MY_SECRET_KEY');
if (orderToken) { // Handle order webhook β check decoded.paymentStatus } else if (refundToken) { // Handle refund webhook β check decoded.refundStatus }} catch (error) { // Invalid signature β reject this webhook}<?php/** * We recommend using Firebase's php-jwt package to verify * Json Web Tokens. You can install it with composer: * > composer require firebase/php-jwt * More information can be found at * https://github.com/firebase/php-jwt */use \Firebase\JWT\JWT;use \Firebase\JWT\Key;
$body = json_decode(file_get_contents('php://input'));
// Determine the webhook type from the POST body$orderToken = $body->orderToken ?? null;$refundToken = $body->refundToken ?? null;$token = $orderToken ?? $refundToken;
try { $decoded = JWT::decode($token, new Key('MY_SECRET_KEY', 'HS256'));
if ($orderToken) { // Handle order webhook β check $decoded->paymentStatus } else if ($refundToken) { // Handle refund webhook β check $decoded->refundStatus }} catch (Exception $e) { // Invalid signature β reject this webhook}?>'''We recommend using the PyJWT package to verifyJson Web Tokens. You can install it with pip:> pip3 install PyJWTMore information can be found athttps://pyjwt.readthedocs.io/en/latest/'''import jwtimport json
body = json.loads(request.body)
# Determine the webhook type from the POST bodyorder_token = body.get('orderToken')refund_token = body.get('refundToken')token = order_token or refund_token
try: decoded = jwt.decode(token, 'MY_SECRET_KEY', algorithms=['HS256'])
if order_token: # Handle order webhook β check decoded['paymentStatus'] pass elif refund_token: # Handle refund webhook β check decoded['refundStatus'] passexcept jwt.exceptions.InvalidSignatureError: # Invalid signature β reject this webhook passFor full details on the decoded token fields and how to act on them:
- Create and validate an Order β Validating the returned Order Token
- Refund an Order β Refund Token contents
Responding to webhooks
Your endpoint must respond with an HTTP status code of 200 OK or 201 Created to acknowledge receipt of the webhook. If your endpoint returns an error, we recommend responding with a JSON body β this helps Montonio troubleshoot delivery issues on your behalf.
If your endpoint does not respond with 200 or 201, Montonio will retry the webhook delivery multiple times over the next 48 hours until a successful response is received.
Testing locally
To test webhooks during local development, you can expose your local server to the internet:
- ngrok β Creates a public tunnel to your local server.
- webhook.site β Useful for quick troubleshooting and inspecting webhook payloads.
For a more detailed walkthrough, see our Help Center article on testing webhooks locally.
Best practices
- Always verify the JWT signature before processing a webhook. Do not trust unverified payloads.
- Respond quickly with a
200or201status code. Process the event asynchronously if needed. - Handle duplicates gracefully. Due to retries, your endpoint may receive the same webhook more than once. Use the order UUID or refund UUID to deduplicate.
- Allowlist Montonioβs IP addresses in your firewall configuration to ensure delivery.