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
- Check the timestamp first: require a valid request-timewithin your allowed clock tolerance. Reject missing, malformed, stale, or excessively future-dated timestamps, and keep your server clock synchronized.
- Read the raw request body before JSON parsing. Preserve all bytes, including whitespace and line breaks.
- Require algorithm=HMAC256and calculate the expected signature:
signedContent = UTF8(request-time + ".") + rawBodyBytes expectedSignature = lowercaseHex(HMAC-SHA256(webhookSecret, signedContent))
- Compare the expected signature with signatureand, 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 FalseAfter 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.
- Connected Accounts, Charge and Referral endpoints: access-tokenis optional, andaccount-idis not supported.
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 OKVerify 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}"