Push Data to Your Platform

Many voice applications don’t just handle calls — they collect and generate valuable data.

With Callr Actions, you can easily send that data to your platform in real time using the fetch@v2 action.

Whether you want to log a call result, store user input, trigger workflows, or enrich CRM records — fetch@v2 lets you POST any data to your own backend over HTTPS.


⚙️ How It Works

  1. During your call flow, collect or compute the data you want to send.
  2. Use fetch@v2 to make an HTTP request to your platform.
  3. Include headers and a JSON body with all the context you need (e.g., variables, CDR, user input).
  4. Optionally capture the response and act on it.

Why is it called "fetch"?

The action fetch@v2 is inspired by the Fetch API used in modern browsers — a standard interface for making HTTP requests across the network.

Like the browser's Fetch API, fetch@v2 supports multiple HTTP methods (GET, POST, PUT, DELETE, etc.) and exposes similar request/response properties:

  • Request: url, method, headers, body
  • Response: headers, status, statusText, body, json

In short, if you've used fetch() in JavaScript, you'll feel right at home using fetch@v2 in your call flows.


📃 Example: Push Inbound Call CDR to Your Server

branches:
  hangup:
    actions:
      - action: fetch@v2
        params:
          url: https://api.example.com/call-logs
          method: POST
          headers:
            content-type: application/json
          body: |
            {
              "cdr": ${{ JSON.stringify(call.cdr) }}
            }

This sends detailed information about the call — the full CDR — to your backend when the call ends.

call is a pre-defined variable.


📲 Example: Push DTMF inputs to Your Server

branches:
  inbound-call:
    - action: gather@v2
      params:
        maxDigits: 1
        prompt:
          what: Press 1 to confirm, or 2 to cancel.
      result: $dtmf

  hangup:
    actions:
      - action: fetch@v2
        params:
          url: https://your-server.com/input
          method: POST
          body: |
            {
              "cdr": ${{ JSON.stringify(call.cdr) }},
              "dtmf": ${{ JSON.stringify($dtmf) }}
            }

This sends detailed information about the call — the full CDR — plus the $dtmf variable to your backend when the call ends.


📼 Example: Push Call Recording & Transcription

branches:
  inbound-call:
    actions:
      - action: startRecording@v1
        events:
          ready:
            - action: fetch@v2
              params:
                url: https://api.example.com/call-recordings
                method: POST
                headers:
                  Content-Type: application/json
                  # If your server requires a header
                  # Authorization: Bearer <secret>
                body: |
                  {
                    "run": ${{ JSON.stringify(run) }},
                    "call": ${{ JSON.stringify(call) }},
                    "scenario": ${{ JSON.stringify(scenario) }},
                    "recordingURL": ${{ JSON.stringify(recording.url) }},
                    "transcription": ${{ JSON.stringify(recording.transcription) }}
                  }

This sends detailed information about the call — the full CDR —, plus the Recording URL (mp3) and the call transcript to your server.

call, run, scenario and recording are pre-defined variables.


🔗Example: Push To OAuth Integrations

🔢 Microsoft Excel

- action: fetch@v2
  params:
    url: https://graph.microsoft.com/v1.0/me/drive/items/<item-id>/workbook/tables/<table>/rows/add
    method: POST
    headers:
      Content-Type: application/json
      Authorization: Bearer ${{ integrations.defaults.microsoft.accessToken }}
    body: |
      {
        "values": [
          [
            ${{ JSON.stringify(call.id) }},
            ${{ JSON.stringify($dial?.callid ?? 0) }},
            ${{ JSON.stringify(scenario.name) }},
            ${{ JSON.stringify(call.fromNumber) }},
            ${{ JSON.stringify(call.toNumber) }},
            ${{ JSON.stringify($dial?.cdr?.callee ?? "") }},
            ${{ JSON.stringify(call.cdr?.started ?? "") }},
            ${{ JSON.stringify(call.cdr?.durationAnswered ?? 0) }}
          ]
        ]
      }
  result: $excel

Here is an example of using the Microsoft Graph API. The Graph API expects an Access Token in an Authorization: Bearer header.

The OAuth Access Tokens are available in the pre-defined variable integrations. The variable integrations.defaults.microsoft.accessToken contains the Access Token of the default Microsoft connection you have selected on the Callr Portal.

To make this request work:

  • Replace <item-id> with a valid Item ID from your OneDrive. You can explore items using this query.
  • Replace <table> with a valid Table name in your file.
  • The Table must have EXACTLY the number of columns you are sending (8 columns in our example).

🧰 Common Use Cases

  • ✅ Log each call into your CRM or analytics platform
  • ✅ Store DTMF input or voice recognition results
  • ✅ Track call outcomes (answered, no-answer, canceled, etc.)
  • ✅ Trigger downstream workflows (e.g. ticket creation, follow-up reminders)

📥 Receive the Data

To receive the data, your server must:

  • Accept HTTPS POST requests
  • Parse JSON input
  • Respond with a 200 OK to confirm receipt

Here’s a few examples:

// Run with: node server.js

const http = require('http');

const server = http.createServer((req, res) => {
  if (req.method === 'POST') {
    let body = '';
    req.on('data', chunk => { body += chunk; });
    req.on('end', () => {
      try {
        const json = JSON.parse(body);
        console.log(JSON.stringify(json, null, 2));
      } catch (e) {
        console.error('Invalid JSON');
      }
      
      res.writeHead(200, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ success: true }));
    });
  } else {
    res.writeHead(405);
    res.end('Method not allowed');
  }
});

server.listen(8888, () => {
  console.log('Server listening on http://localhost:8888');
});
# server.py
# Run with: python server.py

from http.server import BaseHTTPRequestHandler, HTTPServer
import json

class SimpleHandler(BaseHTTPRequestHandler):
    def do_POST(self):
        content_length = int(self.headers.get('Content-Length', 0))
        body = self.rfile.read(content_length)

        try:
            data = json.loads(body)
            pretty = json.dumps(data, indent=2)
            print(pretty)
        except json.JSONDecodeError:
            print("Invalid JSON received")

        self.send_response(200)
        self.send_header('Content-Type', 'application/json')
        self.end_headers()
        self.wfile.write(b'{"success": true}')

    def log_message(self, format, *args):
        return  # Silence default logging

if __name__ == '__main__':
    port = 8888
    print(f"Listening on http://localhost:{port}")
    server = HTTPServer(('0.0.0.0', port), SimpleHandler)
    server.serve_forever()
// Run with: go run main.go

package main

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Only POST allowed", http.StatusMethodNotAllowed)
		return
	}

	body, err := io.ReadAll(r.Body)
	if err != nil {
		log.Println("Error reading body:", err)
		return
	}

	var payload map[string]any
	if err := json.Unmarshal(body, &payload); err != nil {
		log.Println("Invalid JSON:", err)
	} else {
		pretty, _ := json.MarshalIndent(payload, "", "  ")
		fmt.Println(string(pretty))
	}
  
	w.Header().Set("Content-Type", "application/json")
	w.Write([]byte(`{"success": true}`))
}

func main() {
	http.HandleFunc("/", handler)
	fmt.Println("Listening on http://localhost:8888")
	log.Fatal(http.ListenAndServe(":8888", nil))
}
<?php
// run with php -S localhost:8888 server.php

header('Content-Type: application/json');

$data = file_get_contents('php://input');

if (!empty($data)) {
    // pretty print the json data
    error_log(json_encode(json_decode($data), JSON_PRETTY_PRINT));
} else {
    error_log('No data received');
}

// JSON response
echo json_encode(['success' => true]);

🔁 Optional: Use the Response

You can also use the response from your server and store it in a variable:

- action: fetch@v2
  params:
    url: https://api.example.com/profile
    method: POST
    body: |
      {
        "caller": ${{ JSON.stringify(call.fromNumber) }}
      }
  result: $profile

Now $profile.json contains your server’s JSON response — and can be used later in the flow. See also Fetch Data from Your Platform.


🛡️ Best Practices

  • ✅ Always return a 200 OK response from your server.
  • ✅ Use HTTPS endpoints (no HTTP).
  • ✅ Sanitize and validate incoming data on your side.
  • 🚫 Don’t perform long processing — respond quickly and handle work asynchronously if needed.

💡

Tip

Need help debugging? Use ngrok or a tool like Webhook.site to test fetch@v2 requests in real time.