Authentication

The WorldFirst Enhanced API supports two authentication methods:
  • API Key (Suggested): authenticate each request with a Bearer API Key, and verify webhook notifications with an HMAC-SHA256 Webhook Secret. No request signing or response verification is required. Use this method for new integrations.
  • RSA signature: the existing method, where every request is signed with your private key and every response and webhook notification can be verified with WorldFirst's public key. See Appendix: RSA signature authentication.
All API traffic must use HTTPS. Calls made over plain HTTP fail, and requests without valid authentication also fail. You can create and manage your credentials in the Developer Center.

API Key authentication (Suggested)

Get your credentials

Create an API Key integration in the Developer Center, select the APIs it can access, and securely save its credentials.
Use the credentials issued for the environment you are connecting to: sandbox (
TEST
) or production (
PROD
). Copy each value exactly as issued and treat it as an opaque secret; do not decode or modify it.

Authenticate API requests

Include your complete API Key in the
Authorization
header:
Authorization: Bearer <YOUR_API_KEY>
The API Key is used directly; no access-token exchange or request signature is required. You do not need to send
Client-Id
or
Request-Time
, because WorldFirst identifies your integration from the API Key. Do not include an RSA
Signature
header in the same request. To operate on another account, see Act on behalf of another account. Follow the API reference of each endpoint for any additional business headers.
#!/bin/bash
DOMAIN="{domain}"
ENDPOINT="/api/open/v1/payouts/create"
BODY='{"requestId":"TX202608080001","businessSceneCode":"ATOMIC_PAYOUT"}'

curl -X POST "https://${DOMAIN}${ENDPOINT}" \
  -H "Authorization: Bearer ${WORLDFIRST_API_KEY}" \
  -H "Content-Type: application/json; charset=UTF-8" \
  -d "${BODY}"
Load
WORLDFIRST_API_KEY
from your server's secret store. Responses to requests authenticated with an API Key do not carry an application-level signature; receive them over HTTPS.

Verify webhook signatures

For API Key integrations, WorldFirst signs webhook notifications with HMAC-SHA256, using your integration's Webhook Secret. Verify each notification before processing its contents. For RSA signature integrations, see Verify a Webhook Notification.

Read the headers

client-id: <YOUR_CLIENT_ID>
request-time: 2026-08-08T10:20:00Z
signature: algorithm=HMAC256,signature=<HEX_SIGNATURE>,previousSignature=<PREVIOUS_HEX_SIGNATURE>

Verify the notification

  1. Check the timestamp first: require a valid
    request-time
    within your allowed clock tolerance. Reject missing, malformed, stale, or excessively future-dated timestamps, and keep your server clock synchronized.
  1. Read the raw request body before JSON parsing. Preserve all bytes, including whitespace and line breaks.
  1. Require
    algorithm=HMAC256
    and calculate the expected signature:
    signedContent = UTF8(request-time + ".") + rawBodyBytes
    expectedSignature = lowercaseHex(HMAC-SHA256(webhookSecret, signedContent))
  1. Compare the expected signature with
    signature
    and, if present,
    previousSignature
    , using a constant-time comparison. Accept the notification as soon as one matches; reject it only if none match. This lets either secret verify notifications during the overlap period, so you can rotate secrets without interruption.
Use the complete Webhook Secret, including its
iwsc_
prefix, encoded as UTF-8 bytes. Do not Base64-decode it. The signed content contains only the timestamp, one period (
.
), and the raw body. Do not add the URL,
client-id
, or an extra newline, and do not reformat the timestamp or re-serialize the JSON.
When rotating the Webhook Secret, deploy the new secret before the overlap period ends. After that, WorldFirst signs only with the new secret.
The following example requires Python 3.11+ and uses only the standard library. Pass the raw request body as bytes, both header values exactly as received, and the complete Webhook Secret loaded from secure server-side configuration. It returns
True
when verification passes; reject the notification on
False
or on any input-format exception.
import hashlib
import hmac
from datetime import datetime, timezone


def verify_webhook(raw_body, request_time, signature_header, secret,
                   tolerance_seconds=300):
    # Check the timestamp before verifying signatures.
    sent_at = datetime.fromisoformat(request_time)
    if abs((datetime.now(timezone.utc) - sent_at).total_seconds()) > tolerance_seconds:
        return False

    fields = dict(item.strip().split("=", 1) for item in signature_header.split(","))
    if fields.get("algorithm") != "HMAC256":
        return False

    # Use the original timestamp and raw body to compute the HMAC.
    content = (request_time + ".").encode("utf-8") + raw_body
    expected = hmac.new(secret.encode("utf-8"), content, hashlib.sha256).hexdigest()

    # Try both signatures so either secret works during rotation.
    for name in ("signature", "previousSignature"):
        if hmac.compare_digest(expected, fields.get(name, "")):
            return True
    return False
After verification, handle duplicate deliveries safely and acknowledge the notification as required by its API reference. Your webhook response does not need a signature.

Keep your credentials secure

Store both credentials on your server in a secret manager or protected environment variables. Keep them out of browser code, mobile apps, source control, URLs, and logs. Grant only the API permissions your integration needs, and replace credentials promptly if they are exposed.
If an API request fails authentication, check the complete
Bearer
value, the environment, the key's validity, and its API permissions. If webhook verification fails, check the secret, the raw body, the exact timestamp, and the rotation status.

Act on behalf of another account

By default, a request operates on the account that owns your credentials. To operate on another account, add one of the following headers to the request, in addition to your authentication headers. Send only one of them per request.
Most endpoints accept either header. The following endpoints are exceptions:
  • OAuth 2.0 endpoints: do not send either header.

Appendix: RSA signature authentication

RSA signature authentication is the existing method. Every request must be signed with your private key, and every response can be verified using WorldFirst's public key.
To switch an existing RSA integration to API Key authentication, arrange the change with WorldFirst, and update both your API requests and your webhook verification before switching.

Sign a Request

1. Build the Signing String

POST /api/open/v1/{endpoint}
{Client-Id}.{Request-Time}.{Request-Body}
The two lines are joined by a single newline (
\n
). Fields on the second line are dot-separated.

2. Sign and Encode

# Sign with your private key
SIGNATURE=$(printf '%s' "$SIGNING_STRING" | \
  openssl dgst -sha256 -sign private_key.pem | \
  openssl base64 -A)

# URL-encode the result
ENCODED_SIG=$(python3 -c \
  "import urllib.parse, sys; print(urllib.parse.quote(sys.stdin.read(), safe=''))" \
  <<< "$SIGNATURE")

3. Set the Request Headers

Verify a Response

WorldFirst signs every response using the same format — substitute response headers and body:
POST /api/open/v1/{endpoint}
{Client-Id}.{Response-Time}.{Response-Body}
Extract the
signature=
value from the response
Signature
header, URL-decode it, then verify:
printf '%s' "$VERIFY_STRING" | \
  openssl dgst -sha256 -verify worldfirst_public_key.pem \
  -signature <(echo "$DECODED_SIG" | openssl base64 -d -A)
# Expected: Verified OK

Verify a Webhook Notification

WorldFirst signs each webhook notification with its private key, using the same format as requests, and sends the
Client-Id
,
Request-Time
, and
Signature
headers. Verify each notification with WorldFirst's public key before processing its contents:
POST {Notification-Path}
{Client-Id}.{Request-Time}.{Raw-Body}
  • Notification-Path
    : path of your notification URL, without the domain. For example,
    /webhooks/worldfirst
    .
  • Client-Id
    ,
    Request-Time
    : header values exactly as received.
  • Raw-Body
    : the request body exactly as received, before JSON parsing.
Extract the
signature=
value from the
Signature
header, URL-decode it, and verify it as described in Verify a Response.

Complete Example

#!/bin/bash
DOMAIN="{domain}"
ENDPOINT="/api/open/v1/payouts/create"
CLIENT_ID="YOUR_CLIENT_ID"
REQUEST_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
BODY='{"requestId":"TX202608080001","businessSceneCode":"ATOMIC_PAYOUT"}'

SIGNING_STRING=$(printf 'POST %s\n%s.%s.%s' "$ENDPOINT" "$CLIENT_ID" "$REQUEST_TIME" "$BODY")

SIGNATURE=$(printf '%s' "$SIGNING_STRING" | openssl dgst -sha256 -sign private_key.pem | openssl base64 -A)
ENCODED_SIG=$(python3 -c "import urllib.parse, sys; print(urllib.parse.quote(sys.stdin.read(), safe=''))" <<< "$SIGNATURE")

curl -X POST "https://${DOMAIN}${ENDPOINT}" \
  -H "Signature: algorithm=RSA256, keyVersion=2, signature=${ENCODED_SIG}" \
  -H "Content-Type: application/json; charset=UTF-8" \
  -H "Client-Id: ${CLIENT_ID}" \
  -H "Request-Time: ${REQUEST_TIME}" \
  -d "${BODY}"