Amwal Tech logoDocs

Webhook Public Key API

Amwal provides a secured REST endpoint (/api/webhooks/public-key/) to programmatically retrieve, provision, and rotate the RSA 2048-bit public verification key used to validate incoming webhook signatures.

This endpoint is utilized by e-commerce plugins (such as WooCommerce and Magento), headless platforms, and automated CI/CD pipelines to manage webhook credentials without manual dashboard intervention.


Keep Credentials Confidential

The Authorization secret token and private signing keys must remain strictly confidential. Only invoke this endpoint from a secure server-to-server environment. Never expose your secret keys or execute this request from client-side / browser applications.


Endpoint Details

AttributeDetails
MethodsGET, POST
Sandbox URLhttps://backend.sa.amwal.tech/api/webhooks/public-key/
Production URLhttps://backend.sa.amwal.tech/api/webhooks/public-key/
AuthenticationDual-header (Authorization + X-Amwal-Key)
Content-Typeapplication/json

Authentication & Headers

Requests must provide merchant authentication credentials and environment context using the following headers:

HeaderTypeRequiredDescription
AuthorizationStringYesConfidential Merchant Secret Key or Auth Token (e.g., b2c8273d-xxxx-xxxx-xxxx-xxxxxxxxxxxx).
X-Amwal-KeyStringYesMerchant Public Identifier (sandbox-amwal-xxxx for Sandbox or prod-amwal-xxxx for Production).
Content-TypeStringYesMust be set to application/json.
AcceptStringNoRecommended application/json.

1. Retrieve Current Public Key & Endpoint Status (GET)

Use GET to inspect the currently registered RSA public key, key fingerprint, webhook listener endpoint configuration, delivery health statistics, and active event subscriptions.

Request

curl -s -X GET "https://backend.sa.amwal.tech/api/webhooks/public-key/" \
  -H "Authorization: YOUR_MERCHANT_SECRET_TOKEN" \
  -H "X-Amwal-Key: sandbox-amwal-c1edaa49-xxxx-xxxx-xxxx-xxxxxxxxxxxx" \
  -H "Content-Type: application/json"

Success Response (200 OK)

{
  "merchant": {
    "id": "f24267bd-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "name": "Your Store Name"
  },
  "api_key": {
    "id": "399a018e-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "name": "Amwal Webhook Key",
    "type": "sandbox",
    "public_key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...\n-----END PUBLIC KEY-----",
    "key_fingerprint": "6c1d480a214fa0c232768978e9a73c8e33ca13bbecfd5dcf607241048a32b58f",
    "is_active": true,
    "created_at": "2026-07-19T05:28:40.208569+00:00",
    "last_used_at": "2026-10-29T10:31:41.140688+00:00"
  },
  "webhook_endpoint": {
    "id": "cdeefc25-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "url": "https://yourdomain.com/wp-json/wc/amwal/v2/webhooks",
    "description": "Amwal WooCommerce Plugin",
    "is_active": true,
    "is_global": false,
    "created_at": "2026-07-19T05:28:40.200518+00:00",
    "updated_at": "2026-09-10T08:46:43.242834+00:00",
    "success_count": 1284,
    "failure_count": 12,
    "last_success": "2026-10-29T11:25:20.796150+00:00",
    "last_failure": "2026-10-28T09:12:04.140688+00:00"
  },
  "webhook_events": [
    "order.success",
    "order.failed",
    "order.updated",
    "payment_link.expired",
    "modal.closed"
  ]
}

2. Provision or Rotate Public Key (POST)

Use POST to upload, register, or rotate an RSA 2048-bit public key. This is commonly executed during plugin onboarding or scheduled automated key rotations.

Request Body Parameters

FieldTypeRequiredDescription
public_keyStringYesRSA 2048-bit public key formatted as a PEM string with -----BEGIN PUBLIC KEY----- and -----END PUBLIC KEY----- delimiters.

Example cURL Request

curl -s -X POST "https://backend.sa.amwal.tech/api/webhooks/public-key/" \
  -H "Authorization: YOUR_MERCHANT_SECRET_TOKEN" \
  -H "X-Amwal-Key: sandbox-amwal-c1edaa49-xxxx-xxxx-xxxx-xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "public_key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...\n-----END PUBLIC KEY-----"
  }'

Success Response (200 OK)

The response returns the updated configuration and computed key_fingerprint:

{
  "merchant": {
    "id": "f24267bd-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "name": "Your Store Name"
  },
  "api_key": {
    "id": "399a018e-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "name": "Amwal Webhook Key",
    "type": "sandbox",
    "public_key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...\n-----END PUBLIC KEY-----",
    "key_fingerprint": "6c1d480a214fa0c232768978e9a73c8e33ca13bbecfd5dcf607241048a32b58f",
    "is_active": true,
    "created_at": "2026-10-29T11:30:00.000000+00:00",
    "last_used_at": null
  },
  "webhook_endpoint": {
    "id": "cdeefc25-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "url": "https://yourdomain.com/wp-json/wc/amwal/v2/webhooks",
    "description": "Amwal WooCommerce Plugin",
    "is_active": true,
    "is_global": false,
    "created_at": "2026-07-19T05:28:40.200518+00:00",
    "updated_at": "2026-10-29T11:30:00.000000+00:00",
    "success_count": 1284,
    "failure_count": 12,
    "last_success": "2026-10-29T11:25:20.796150+00:00",
    "last_failure": "2026-10-28T09:12:04.140688+00:00"
  },
  "webhook_events": [
    "order.success",
    "order.failed",
    "order.updated",
    "payment_link.expired",
    "modal.closed"
  ]
}

Response Data Dictionary

1. merchant

FieldTypeDescription
idUUID StringUnique identifier of the merchant organization.
nameStringRegistered business or display name of the merchant.

2. api_key

FieldTypeDescription
idUUID StringUnique identifier assigned to the webhook key record.
nameStringDescriptive identifier of the key (e.g., plugin name or custom label).
typeStringActive environment mode (sandbox or production).
public_keyPEM StringThe 2048-bit RSA public key string used to verify incoming X-Signature headers with RSA-PSS SHA-256.
key_fingerprintHex StringSHA-256 fingerprint hash of the public key. Echoed in outgoing webhook dispatches via the X-Api-Key header to enable zero-downtime key rotation.
is_activeBooleanIndicates whether this key is actively used to sign webhook deliveries.
created_atISO 8601UTC timestamp of key creation.
last_used_atISO 8601 | nullTimestamp of the most recent signature generation using this keypair.

3. webhook_endpoint

FieldTypeDescription
idUUID StringUnique identifier of the registered webhook listener.
urlHTTPS URLDestination URL where Amwal dispatches webhook events.
descriptionStringDescriptive notes for the listener endpoint.
is_activeBooleanWhether webhook dispatching is enabled for this endpoint.
is_globalBooleanWhether this endpoint receives events across all sub-accounts.
created_atISO 8601Initial registration timestamp of the endpoint.
updated_atISO 8601Last modification timestamp of the endpoint settings.
success_countIntegerTotal count of successfully acknowledged dispatches (2xx responses).
failure_countIntegerTotal count of failed deliveries (connection timeouts, 4xx, or 5xx responses).
last_successISO 8601 | nullTimestamp of the last successful dispatch delivery.
last_failureISO 8601 | nullTimestamp of the last failed dispatch attempt.

4. webhook_events

Array of event strings currently subscribed to by this endpoint. For full payload specifications, see Webhook Event Types.

  • order.success: Payment authorization and capture completed.
  • order.failed: Payment authorization rejected or cancelled.
  • order.updated: Transaction refunded or adjusted.
  • payment_link.expired: Payment link reached expiration before completion.
  • modal.closed: Customer dismissed checkout window without completing payment.

Error & Validation Handling

Review the possible HTTP error codes and validation responses:

1. 401 Unauthorized

Occurs when the Authorization header is missing, formatted incorrectly, or invalid.

{
  "detail": "Authentication credentials were not provided."
}

Or when an invalid or expired token is passed:

{
  "detail": "Not Authenticated"
}

2. 400 Bad Request — Missing Public Key

Returned when calling POST without including the required public_key field in the request body:

{
  "public_key": [
    "This field is required."
  ]
}

3. 400 Bad Request — Invalid RSA PEM Format

Returned when the provided string is not a valid 2048-bit RSA public key in PEM format:

{
  "public_key": [
    "Invalid public key format. Must be a valid RSA 2048-bit PEM key."
  ]
}

RSA Key Generation Requirement

The public key must use 2048-bit RSA encryption and adhere to the standard X.509 SubjectPublicKeyInfo PEM format starting with -----BEGIN PUBLIC KEY-----.

4. 403 Forbidden — Scope or Organization Mismatch

Returned if the X-Amwal-Key does not match the merchant organization bound to the Authorization secret token, or if the user lacks administrative permissions.

{
  "detail": "You do not have permission to perform this action."
}

5. 404 Not Found — Unconfigured Endpoint

Returned if a webhook listener URL has not yet been registered for the merchant account. Amwal requires a destination Webhook URL before keys can be provisioned.

{
  "detail": "No webhook endpoint configured. Please register a Webhook URL first."
}

Code Examples

Node.js (TypeScript)

import axios from 'axios';

interface WebhookKeyResponse {
  merchant: { id: string; name: string };
  api_key: {
    id: string;
    public_key: string;
    key_fingerprint: string;
    is_active: boolean;
  };
  webhook_endpoint: {
    url: string;
    success_count: number;
    failure_count: number;
  };
  webhook_events: string[];
}

const AMWAL_SECRET = process.env.AMWAL_SECRET_KEY!;
const AMWAL_PUBLIC = process.env.AMWAL_MERCHANT_KEY!; // sandbox-amwal-xxx or prod-amwal-xxx

// 1. Fetch Current Public Key & Endpoint Health
async function getWebhookPublicKey(): Promise<WebhookKeyResponse> {
  const response = await axios.get<WebhookKeyResponse>(
    'https://backend.sa.amwal.tech/api/webhooks/public-key/',
    {
      headers: {
        'Authorization': AMWAL_SECRET,
        'X-Amwal-Key': AMWAL_PUBLIC,
        'Content-Type': 'application/json',
      },
    }
  );
  return response.data;
}

// 2. Provision or Rotate Public Key
async function updateWebhookPublicKey(pemPublicKey: string): Promise<WebhookKeyResponse> {
  const response = await axios.post<WebhookKeyResponse>(
    'https://backend.sa.amwal.tech/api/webhooks/public-key/',
    { public_key: pemPublicKey },
    {
      headers: {
        'Authorization': AMWAL_SECRET,
        'X-Amwal-Key': AMWAL_PUBLIC,
        'Content-Type': 'application/json',
      },
    }
  );
  return response.data;
}

Python

import requests
import os

AMWAL_SECRET = os.getenv("AMWAL_SECRET_KEY")
AMWAL_PUBLIC = os.getenv("AMWAL_MERCHANT_KEY")
ENDPOINT_URL = "https://backend.sa.amwal.tech/api/webhooks/public-key/"

headers = {
    "Authorization": AMWAL_SECRET,
    "X-Amwal-Key": AMWAL_PUBLIC,
    "Content-Type": "application/json"
}

# 1. Fetch Key & Endpoint Status
response = requests.get(ENDPOINT_URL, headers=headers)
if response.status_code == 200:
    data = response.json()
    print("Active Fingerprint:", data["api_key"]["key_fingerprint"])
    print("Endpoint URL:", data["webhook_endpoint"]["url"])
    print("Deliveries - Success:", data["webhook_endpoint"]["success_count"])

# 2. Provision New Public Key
new_key_pem = """-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----"""

update_resp = requests.post(ENDPOINT_URL, headers=headers, json={"public_key": new_key_pem})
if update_resp.status_code == 200:
    print("Key updated successfully!")

PHP

<?php

$secretKey = getenv('AMWAL_SECRET_KEY');
$merchantKey = getenv('AMWAL_MERCHANT_KEY');
$url = 'https://backend.sa.amwal.tech/api/webhooks/public-key/';

$headers = [
    'Authorization: ' . $secretKey,
    'X-Amwal-Key: ' . $merchantKey,
    'Content-Type: application/json',
    'Accept: application/json'
];

// 1. Get Webhook Key & Health Status
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($httpCode === 200) {
    $data = json_decode($response, true);
    echo "Webhook URL: " . $data['webhook_endpoint']['url'] . "\n";
    echo "Key Fingerprint: " . $data['api_key']['key_fingerprint'] . "\n";
}

Zero-Downtime Key Rotation Workflow

When rotating keys in production, backends can support dual validation without dropping dispatches:

Rendering diagram...
  1. Deploy New Key to Receiver: Update your webhook listener server to accept signatures from both the previous key and the new key, indexing keys by key_fingerprint.
  2. Call Provisioning Endpoint: Call POST /api/webhooks/public-key/ with your new 2048-bit RSA public key.
  3. Verify Incoming Dispatches: Future webhook dispatches will carry the new key's fingerprint in the X-Api-Key header.
  4. Decommission Old Key: Once all in-flight dispatches have settled, retire the old key from your backend cache.

On this page