Description
What is cURL?
cURL (the curl command, built on the open-source libcurl library) is a command-line tool for making HTTP requests. For the WhatsApp Cloud API it is the shortest path from an access token to a delivered message: one command sets the method, a URL such as https://graph.facebook.com/v26.0/{PHONE_NUMBER_ID}/messages, the Authorization: Bearer header and a JSON body, then prints the Graph API's response. The sample request in the WhatsApp section of Meta's App Dashboard is a curl command for exactly this reason.
A curl command is a handful of flags that map one-to-one onto the HTTP request: -X POST sets the method, each -H adds a header, -d (or --data) sends a request body, and -F builds a multipart form for file uploads such as the media upload endpoint. -i prints the status line and response headers above the body, which is where you see whether the call returned 200 or an error.
cURL is a tool for exploring, testing and debugging rather than for production sending. It shows exactly what goes over the wire, with no SDK defaults or saved GUI state in between, which makes it the common language of documentation, bug reports and support tickets. A Postman Collection stores the same requests in a GUI with variables and environments, and an SDK wraps them in code; both can import a curl command or be reproduced as one.
Key takeaways
- cURL is a command-line HTTP client: one command sends a complete Graph API request with headers, a JSON body or a file upload.
- Every Cloud API call needs an
Authorization: Bearerheader; JSON bodies also needContent-Type: application/json. - Media uploads use multipart form fields (
-F), not a JSON body, and curl sets the multipart boundary for you. - curl exits successfully on an HTTP 400 unless you pass
--fail-with-body, so always look at the status code. - On Windows call
curl.exeexplicitly and send JSON from a file: cmd does not treat single quotes as quotes.
Usage
CURL in code
curl -i -X POST "https://graph.facebook.com/v26.0/{PHONE_NUMBER_ID}/messages" \-H "Authorization: Bearer $WA_TOKEN" \-H "Content-Type: application/json" \-d '{"messaging_product": "whatsapp","to": "919812345678","type": "template","template": { "name": "hello_world", "language": { "code": "en_US" } }}'
A template can be sent at any time to an opted-in user; free-form text needs an open customer service window.
Mechanics
How does cURL work?
step 1 — Set the endpoint and method
Build the URL from the current Graph API version and the object you act on:
/{PHONE_NUMBER_ID}/messagesto send,/{PHONE_NUMBER_ID}/mediato upload,/{WABA_ID}/message_templatesto list templates. Pass-X POSTfor writes; a GET needs no-X.step 2 — Add the auth header
Pass
-H "Authorization: Bearer $WA_TOKEN"with the access token in an environment variable, so it stays out of shell history, screenshots and pasted commands.step 3 — Send a JSON body
Add
-H "Content-Type: application/json"and the body with-d, or use--json(curl 7.82 and later), which sets the content type and Accept headers for you.-d @body.jsonreads the body from a file and sidesteps quoting problems.step 4 — Upload files as multipart
For media, switch to
-Ffields:-F "messaging_product=whatsapp"and-F "file=@invoice.pdf;type=application/pdf". Do not add a Content-Type header by hand; curl writes it with the boundary.step 5 — Read the status, not just the body
curl reports success whenever a response arrives, even a 400. Add
-ito see the status line,-w "%{http_code}"to print it, or--fail-with-body(curl 7.76+) in scripts so an API error stops the script and still shows the error JSON.
Properties
CURL: key facts and limits
- Send endpoint
- POST /v26.0/{PHONE_NUMBER_ID}/messages
- Auth header
- Authorization: Bearer {ACCESS_TOKEN}
- JSON body
- -d with Content-Type, or --json (curl 7.82+)
- File upload
- -F fields (multipart/form-data)
- Show the status
- -i, or -w "%{http_code}"
- Fail on HTTP errors
- --fail-with-body (curl 7.76+)
- On Windows
- curl.exe ships with Windows 10 (1803) and later
Example
Example: Smoke-testing a new number in Kochi
A developer at a Kochi travel agency has just registered a business number and wants proof it can send before wiring up the booking system. She exports the token as WA_TOKEN, runs the curl command for the hello_world template to her own phone and gets back a WAMID. A second command with -F uploads a sample itinerary PDF and returns a media ID; a third sends that ID as a document message. When she reruns the commands the next morning they fail with error 190: she had used the 24-hour temporary token from the App Dashboard, so she switches to a system user access token before handing the commands to the backend team.
Why it matters
Why does cURL matter?
When an integration fails, the first question is whether the problem is your code or the request itself. Replaying the call with cURL strips away frameworks, retries and serialisers: if the curl command works, the bug is in your application; if it fails, the Graph API's error body says why. Curl commands are also the format Meta's docs, provider docs and support teams share, so reading and adapting one quickly saves hours.
Caveats
Common mistakes with cURL
Pasting the token into the command
A literal token ends up in shell history, CI logs and the screenshot you share in a ticket. Keep it in an environment variable or a secrets file, and rotate it at once if it leaks.
Treating exit code 0 as success
curl only fails on transport problems by default. A 400 with an error JSON is a finished request, so scripts must check the status code or use
--fail-with-body.Quoting JSON on Windows
cmd.exe does not treat single quotes as quotes, and in Windows PowerShell 5.1
curlis not curl at all. Callcurl.exeand send the body with-d @body.json.Setting Content-Type on multipart uploads
Adding
Content-Type: multipart/form-databy hand drops the boundary curl generates, and the upload is rejected. Let-Fset the header.Copying samples with an old API version
Old snippets carry old version numbers. Each Graph API version stops working two years after its successor ships, so update the version in the URL when you reuse a command.
Notes
Best practices for cURL
- Keep one
body.jsonper message type and send it with-d @body.json: easier to edit, diff and share than inline JSON. - Pipe responses through
jqto pull out fields, for examplejq -r '.messages[0].id'for the WAMID. - Use
-vonly to debug TLS or redirects: it prints the Authorization header, so never paste verbose output publicly. - A working curl command is the most portable bug report: Postman and most API tools can import it in one click.
Questions
cURL FAQs
- How do I send a WhatsApp message with cURL?
- POST a JSON body to
/{PHONE_NUMBER_ID}/messageswith a Bearer token. Includemessaging_product: whatsapp, the recipient into, atypeand the matching object; outside the 24-hour customer service window that type must betemplate. - Why does curl report success when the WhatsApp API returned an error?
- Because curl only fails on network or transport errors by default. An HTTP 400 with an error body is a completed request; add
--fail-with-bodyor print%{http_code}to treat it as a failure. - Should I test the WhatsApp API with cURL or Postman?
- Use whichever suits the task. cURL is exact and scriptable; Postman is easier for exploring with saved variables and sharing with a team. They are interchangeable, because Postman imports and exports curl commands.
- How do I upload media to the WhatsApp Cloud API with curl?
- Use multipart form fields:
-F "messaging_product=whatsapp"and-F "file=@path;type=MIME"against/{PHONE_NUMBER_ID}/media. The response holds a media ID that you then send in an image, document, audio or video message.