Skip to main content

Meta compatibility

Joinotify exposes the whole Meta Cloud API under the /v1 prefix. If you already have code written against the Graph, migrating is a two-string change.

The substitution​

FromTo
https://graph.facebook.com/v21.0/https://api.joinotify.com/v1/
Authorization: Bearer <META_TOKEN>Authorization: Bearer sk_live_xxx

Nothing else changes. Paths, request bodies, responses, status codes and error messages stay identical — we pass Meta's response through byte for byte.

Before — calling Meta directly
curl -X POST https://graph.facebook.com/v21.0/106540352242922/messages \
-H 'Authorization: Bearer EAAG...' \
-H 'Content-Type: application/json' \
-d '{ "messaging_product": "whatsapp", "to": "5541987111527", "type": "text", "text": { "body": "Hello!" } }'
After — through Joinotify
curl -X POST https://api.joinotify.com/v1/106540352242922/messages \
-H 'Authorization: Bearer sk_live_xxx' \
-H 'Content-Type: application/json' \
-d '{ "messaging_product": "whatsapp", "to": "5541987111527", "type": "text", "text": { "body": "Hello!" } }'

Where to find the IDs​

The mirror needs the same identifiers Meta uses: phone_number_id, waba_id and, for some endpoints, business_id. If all you have is the Joinotify token, find them with:

curl https://api.joinotify.com/me \
-H 'Authorization: Bearer sk_live_xxx'

The token always goes in the header​

Meta's examples often pass the token as ?access_token= in the URL. The mirror does not accept that. The token has to be in Authorization.

# Correct
curl 'https://api.joinotify.com/v1/{WABA_ID}?fields=analytics' \
-H 'Authorization: Bearer sk_live_xxx'

# Does not work
curl 'https://api.joinotify.com/v1/{WABA_ID}?fields=analytics&access_token=sk_live_xxx'

Internally, each Meta endpoint expects the token in a different way — the proxy sorts that out for you. Your real Meta credential never reaches your code.

Blocked endpoints​

Some paths would change the configuration of our Meta integration and affect other customers. They return 403 with type: forbidden_endpoint:

Path segmentWhy it's blocked
subscribed_appsRe-subscribing the app would break webhook routing.
register · deregisterNumber registration happens in the dashboard, during onboarding.
request_code · verify_codeSMS/voice verification belongs to the connection flow.
assigned_users · system_usersUser management for our Business Manager.
debug_token · oauthWould expose metadata about the real credential.
uploadsResumable upload; use POST /v1/{phone_number_id}/media.

A direct POST on a number ID (POST /v1/{phone_number_id} with no sub-resource) is blocked too.

403 Forbidden
{
"error": {
"message": "This endpoint is not available through the Joinotify proxy.",
"type": "forbidden_endpoint"
}
}

Resource ownership​

The first segment after /v1/ has to be a phone_number_id or waba_id that belongs to your account. If it isn't, the response is 403 forbidden — the check happens before any call to Meta.

The exception is media IDs (GET /v1/{media_id}), which cannot be matched up front: in that case Meta decides ownership.

Proxy errors​

When Meta responds, you get its response — errors included, in its format. The Joinotify envelope only shows up for failures that happen before reaching them:

HTTPtypeSituation
400invalid_requestNo resource ID after /v1/.
403forbidden_endpointPath on the block list.
403forbiddenResource does not belong to your account.
502upstream_errorCould not reach Meta.
504upstream_errorMeta took longer than 30 seconds.

What about webhooks?​

You don't configure a callback on Meta — we receive the events and forward them to the endpoints you register, in the same envelope ({ object, entry[].changes[].value }). Handlers written for Meta work unchanged; only signature validation moves from X-Hub-Signature-256 to X-Joinotify-Signature-256.

Details in Webhooks.

Endpoints not listed here​

The reference covers the most-used endpoints. For anything else, check Meta's official documentation and apply the substitution at the top of this page.