Skip to content

Recipe: your own AI endpoint (send text, get an answer)

Make a web address of your own that translates text with Claude. Any program that can send a web request (your website, a spreadsheet script, an app, curl) sends the text and gets the translation back straight away, as JSON.

Time needed 25 minutes
You need An n8n agent with your Claude key (Sign in and build your first AI agent), and a terminal with curl to test (we used one on Linux)
Result curl sends {"text": "Hello! Your order of 2 birthday cakes is ready...", "language": "European Portuguese"} and gets back {"language":"European Portuguese","translation":"Olá! A sua encomenda de 2 bolos de aniversário está pronta..."} in about 1 second

This is the finished workflow:

The finished workflow

Webhook receives the text → Has text? (an If step) checks that there is something to translate → Claude: translate (the Anthropic step) translates it → Respond to Webhook sends the translation back. When the text is missing, Respond: error answers with an error instead.

The difference from a normal webhook (Receive data with a webhook) is the answer: a normal webhook replies at once with Workflow was started. Here the caller waits, and gets the result of the workflow.

Anyone who knows the address can use it, on your Claude key

Each call is a paid request to Claude. Do not publish the address on a public page. The Webhook step has Authentication options to require a password or key; we did not use them in this recipe. Unpublish the workflow when you do not need it.

1. Receive the text (Webhook)

  1. Click the + at the top of the menu on the left, choose New workflow and name it, for example AI translate endpoint.
  2. Click Add first step and choose On webhook call.
  3. Set the Webhook step:

    The Webhook step

    1. HTTP Method: POST.
    2. Path: translate.
    3. Respond: Using 'Respond to Webhook' Node. This is the important setting: n8n waits for the Respond to Webhook step and sends its answer back to the caller.
    4. Test URL / Production URL: the two addresses of the webhook. Ours were https://lusovps-n8n.agents.lsvps.net/webhook-test/translate and .../webhook/translate; yours start with your own n8n address.
    5. After a test call (next point), OUTPUT shows what was sent under body: text and language.
  4. Send one test call now, so the next steps have data to work with. Click Listen for test event, then run in a terminal (with your own address):

    curl -X POST https://yourname-n8n.agents.lsvps.net/webhook-test/translate \
      -H 'Content-Type: application/json' \
      -d '{"text":"Hello! Your order of 2 birthday cakes is ready. You can pick it up on Saturday between 9:00 and 13:00.","language":"European Portuguese"}'
    

    At this point there is no Respond to Webhook step yet, so the call does not get a translation back. That is fine: we only need the data in OUTPUT.

2. Check that there is text (If)

Without this step, a call with no text still goes to Claude. In our test, a call with only {"language":"French"} got back an invented sentence: "The water level has receded, revealing hidden rocks." An instruction in the System Message (If there is no text, reply exactly: NO TEXT RECEIVED) did not fix it reliably: in two tries, Claude answered two different sentences. An If step does.

  1. Close the panel. Click the small + at the right of the Webhook step, search for If and click it. Rename it Has text? (click the name at the top of the panel).
  2. Set the condition:

    The If step

    1. In the left box, paste {{ $json.body.text }}; in the operator list choose String → is not empty.
    2. After a call without text, the item goes to the False Branch, as here. With text, it goes to the True Branch.

3. Translate it (Anthropic)

  1. Close the panel. Click the + at the true exit of Has text?, search for Anthropic, click Anthropic and choose Message a model (under TEXT ACTIONS). We renamed the step Claude: translate.
  2. Set it:

    The Anthropic step

    1. Credential: your Anthropic account (n8n picks it by itself if you have only one).
    2. Model: From list → a Claude model. We used Claude Sonnet 5.
    3. Prompt: click Expression and paste the text below. Under the box, n8n shows the result with the test data: Translate this text into European Portuguese: ...

      Translate this text into {{ $json.body.language }}:
      
      {{ $json.body.text }}
      
  3. Scroll down. Under Options, click Add Option → System Message and paste the fixed instructions:

    You are a professional translator for a small business. Translate the text you receive into the language requested. Keep the meaning, names, numbers and line breaks. Reply with the translation only, no comments.
    
  4. Click Execute step:

    The System Message and the answer

    1. The System Message.
    2. OUTPUT: the translation is under content → text. Keep Simplify Output on, so the answer has this short form.

4. Send the answer back (Respond to Webhook)

  1. Close the panel. Click the + at the right of Claude: translate, search for Respond to Webhook and click it.
  2. Set it:

    The Respond to Webhook step

    1. Respond With: JSON.
    2. Response Body: click Expression and paste:

      {
        "language": {{ JSON.stringify($('Webhook').item.json.body.language) }},
        "translation": {{ JSON.stringify($json.content[0].text) }}
      }
      
  3. Click the small arrows at the bottom right of the Response Body box to see the expression and its result side by side:

    The expression and the JSON it makes

    1. The expression. $('Webhook').item.json.body.language takes the language from the Webhook step; $json.content[0].text is the translation from the step just before.
    2. The Result: the JSON the caller will receive.

Why JSON.stringify?

A translation can contain quotes (") or line breaks, which would break the JSON. JSON.stringify(...) writes the text in a safe way, with the quotes around it. In our test, a text on three lines came back with its line breaks (\n) intact.

5. Answer with an error when the text is missing

  1. Click the + at the false exit of Has text? and add another Respond to Webhook. Rename it Respond: error.
  2. Set it:

    The error answer

    1. Respond With: JSON.
    2. Response Body (Fixed is enough here, there is nothing to work out):

      {
        "error": "Send JSON with the fields text and language"
      }
      
    3. Options → Add option → Response Code: 400. That is the standard web code for "your request is wrong", so the calling program knows it failed.

6. Test the whole workflow

  1. Close the panel and click Execute workflow at the bottom of the canvas. n8n waits for one call to the Test URL.
  2. Run the same curl as in step 1. This time the terminal prints the answer:

    {"language":"European Portuguese","translation":"Olá! A sua encomenda de 2 bolos de aniversário está pronta. Pode levantá-la no sábado entre as 9:00 e as 13:00."}
    

    We checked the translation: correct, with the 2 cakes, Saturday and the times kept. In the canvas, the steps of the true path have green ticks.

7. Publish and use the production address

  1. Click Publish at the top right, then Publish in the window.
  2. From now on, use the Production URL (/webhook/ instead of /webhook-test/). It works every time, without Execute workflow. Our production tests:

    curl -X POST https://yourname-n8n.agents.lsvps.net/webhook/translate \
      -H 'Content-Type: application/json' \
      -d '{"text":"Guten Tag, ich möchte am Freitag einen glutenfreien Kuchen für 8 Personen bestellen. Ist das möglich?","language":"English"}'
    
    We sent We got back
    The German text above, "language":"English" {"language":"English","translation":"Hello, I would like to order a gluten-free cake for 8 people on Friday. Is that possible?"} in 1.1 seconds
    {"language":"French"} (no text) {"error":"Send JSON with the fields text and language"} with code 400
    hello (not JSON at all) the same error, code 400
  3. When you no longer need it, click the arrow next to Published → Unpublish. The address then answers The requested webhook "POST translate" is not registered. (code 404).

Check that it worked

  • curl to the Production URL prints a JSON answer with translation, not Workflow was started.
  • A call without text gets the error and code 400, and no Claude request is made.
  • The Executions tab of the workflow has one run per call (how to read it).

Make it yours

The same five steps make other AI tools. Change the Prompt, the System Message and the field name in Respond to Webhook, for example:

  • Summarise: Summarise this text in 3 bullet points, in the language of the text: {{ $json.body.text }}, and answer "summary".
  • Reply suggestion: Write a short, polite reply to this customer message: {{ $json.body.text }}.
  • Sort messages: Answer only one word, sales, support or spam: {{ $json.body.text }}.

Test each new prompt with a few real texts before you rely on it.

Common problems

The call answers {\"message\":\"Workflow was started\"} instead of the translation

The Webhook step has Respond set to Immediately. Set it to Using 'Respond to Webhook' Node, then Publish again.

It answers with an invented text

The call had no text field (check its spelling: text, lower case) and the workflow has no Has text? step. See step 2.

This webhook is not registered for GET requests

The caller used GET (for example, the address typed in a browser). In our test the full answer was This webhook is not registered for GET requests. Did you mean to make a POST request? Send a POST with a JSON body, as in the curl examples.

The requested webhook \"POST translate\" is not registered

For /webhook/: the workflow is not published. For /webhook-test/: click Execute workflow (or Listen for test event) first; the test address works for one call only. See Receive data with a webhook.

Next steps