Skip to main content
Webhooks allow RevRing to communicate with your systems before and after phone calls, enabling deep integration with your CRM, databases, and business logic.

Webhook Types

RevRing supports two types of webhooks:
  • Pre-Call Webhooks: Called before an inbound call is answered, allowing you to personalize the agent’s behavior
  • Post-Call Webhooks: Called after any call ends, providing complete call details including transcript and recording

Pre-Call Webhooks

Pre-call webhooks enable dynamic customization of agent behavior based on caller information. Before answering an inbound call, RevRing sends caller details to your webhook and uses the response to personalize the conversation.

When to Use

  • Look up caller information in your CRM
  • Customize greetings based on customer status or history
  • Route calls differently based on caller context
  • Apply business hours logic or special handling
  • Personalize the agent’s knowledge with customer-specific data

Configuration

  1. Navigate to your agent’s Advanced tab
  2. Set the Pre-call Webhook URL:
    https://api.yourcompany.com/webhooks/pre-call
    
  3. Click Save
Pre-call webhooks only apply to inbound calls. Outbound calls receive variables directly via the API when the call is sent.

Request Format

When an inbound call arrives, RevRing sends a POST request to your webhook:
{
  "event": "pre_call",
  "agentId": "agent_456def",
  "fromNumber": "+14155551234",
  "toNumber": "+12025559999"
}
Field Descriptions:
  • event: Always "pre_call" for pre-call webhooks
  • agentId: The agent that will handle the call
  • fromNumber: Caller’s phone number (E.164 format)
  • toNumber: The number that was dialed

Response Format

Your webhook must respond within 3 seconds with a JSON object containing variables:
{
  "variables": {
    "customer_name": "John Smith",
    "account_status": "premium",
    "last_order_date": "March 10, 2024",
    "outstanding_balance": "$0.00",
    "preferred_language": "English"
  }
}
These variables will be available in the agent’s prompt and first message using {{variable_name}} syntax.
Variables you return can override system variables like {{current_time}} and {{timezone}} if needed. However, system variables ({{direction}}, {{user_number}}, {{agent_number}}, {{current_time}}, {{timezone}}, {{language}}) are automatically provided and don’t need to be set via webhook.

Using Variables in Agent Configuration

Reference webhook variables in your First Message:
Hello {{customer_name}}, thank you for calling. I see you're a {{account_status}} member. How can I help you today?
And in your Prompt:
You are a customer service agent. You're speaking with {{customer_name}}, who is a {{account_status}} customer.

Their account information:
- Last order: {{last_order_date}}
- Balance: {{outstanding_balance}}
- Preferred language: {{preferred_language}}

Use this context to provide personalized assistance.

Error Handling

If your webhook fails, times out, or returns invalid JSON:
  • RevRing will proceed with the call using default variables (if configured)
  • The call will not be rejected - the agent will answer without personalization
  • Check the call’s preCallWebhookStatus field to see webhook delivery status
Webhook Status Values:
  • success: Webhook responded successfully
  • failed: Webhook returned an error (4xx/5xx status)
  • timeout: Webhook didn’t respond within 3 seconds
  • null: No webhook configured

Example Implementation

Node.js / Express:
app.post('/webhooks/pre-call', async (req, res) => {
  const { fromNumber, agentId } = req.body;
  
  try {
    // Look up customer in your CRM
    const customer = await crm.findByPhone(fromNumber);
    
    if (customer) {
      res.json({
        variables: {
          customer_name: customer.name,
          account_status: customer.tier,
          last_order_date: customer.lastOrderDate,
          outstanding_balance: customer.balance,
          vip: customer.isVIP ? "yes" : "no"
        }
      });
    } else {
      // Unknown caller
      res.json({
        variables: {
          customer_name: "valued customer",
          account_status: "new",
          vip: "no"
        }
      });
    }
  } catch (error) {
    console.error('Pre-call webhook error:', error);
    // Return minimal data rather than erroring
    res.json({ variables: { customer_name: "caller" } });
  }
});
Python / Flask:
@app.route('/webhooks/pre-call', methods=['POST'])
def pre_call_webhook():
    data = request.json
    from_number = data.get('fromNumber')
    
    try:
        # Look up customer
        customer = crm.find_by_phone(from_number)
        
        if customer:
            return jsonify({
                'variables': {
                    'customer_name': customer.name,
                    'account_status': customer.tier,
                    'last_order_date': customer.last_order_date,
                    'outstanding_balance': f"${customer.balance:.2f}"
                }
            })
        else:
            return jsonify({
                'variables': {
                    'customer_name': 'valued customer',
                    'account_status': 'new'
                }
            })
    except Exception as e:
        logger.error(f'Pre-call webhook error: {e}')
        return jsonify({'variables': {'customer_name': 'caller'}}), 200

Post-Call Webhooks

Post-call webhooks deliver complete call details immediately after a call ends, including the transcript, recording URL, summary, and metadata.

When to Use

  • Update your CRM with call outcomes
  • Log call details for compliance or auditing
  • Trigger follow-up workflows (emails, tasks, tickets)
  • Track agent performance metrics
  • Store transcripts and recordings in your system
  • Implement custom analytics or reporting

Configuration

  1. Navigate to your agent’s Overview tab
  2. Set the Post-call Webhook URL:
    https://api.yourcompany.com/webhooks/post-call
    
  3. Click Save
Post-call webhooks fire for both inbound and outbound calls.

Request Format

After a call ends, RevRing sends a POST request with complete call details:
{
  "id": "a3c18021-192f-565e-87b5-5af428f29e81",
  "direction": "INBOUND",
  "status": "COMPLETED",
  "fromNumber": "+14155551234",
  "toNumber": "+12025559999",
  "recordingUrl": "https://secure.revring.ai/recordings/a3c18021-192f-565e-87b5-5af428f29e81.ogg",
  "hangupCause": "END_CALL_TOOL",
  "errorMessage": null,
  "transcript": [
    {
      "at": 0,
      "seq": 0,
      "role": "assistant",
      "text": "Hello, how can I help you today?",
      "type": "message",
      "turnId": "item_1a1f40b6737f",
      "isFinal": true
    },
    {
      "at": 4.588,
      "seq": 1,
      "role": "user",
      "text": "I have a question about my order.",
      "type": "message",
      "turnId": "user_71be2a_079a32",
      "isFinal": true,
      "language": "en-US"
    },
    {
      "at": 18.242,
      "seq": 2,
      "role": "tool",
      "tool": {
        "args": "{\"order_id\": \"ORD-12345\"}",
        "name": "check_order_status"
      },
      "type": "tool_call",
      "result": {
        "output": "{\"status\": \"shipped\", \"tracking\": \"1Z999AA10123456784\"}"
      },
      "turnId": "tool_10cde5_59dcaf"
    },
    {
      "at": 20.5,
      "seq": 3,
      "role": "assistant",
      "text": "Your order has been shipped. The tracking number is 1Z999AA10123456784.",
      "type": "message",
      "turnId": "item_2aba75c0b341",
      "isFinal": true
    },
    {
      "at": 28.5,
      "seq": 4,
      "role": "user",
      "text": "Thanks, that's all I needed.",
      "type": "message",
      "turnId": "user_728ab3_4f2a1c",
      "isFinal": true,
      "language": "en-US"
    },
    {
      "at": 30.2,
      "seq": 5,
      "role": "tool",
      "tool": {
        "args": "{}",
        "name": "end_call"
      },
      "type": "tool_call",
      "result": {
        "output": "Ending call."
      },
      "turnId": "tool_7281cd_8717e4"
    }
  ],
  "summary": "Customer inquired about their order status. Agent checked the order and confirmed it has been shipped with tracking number provided.",
  "metrics": {
    "llm_prompt_tokens": 7585,
    "stt_audio_duration": 58.65,
    "tts_audio_duration": 27.14,
    "tts_characters_count": 491,
    "llm_completion_tokens": 114,
    "llm_input_audio_tokens": 0,
    "llm_output_audio_tokens": 0,
    "llm_prompt_cached_tokens": 819
  },
  "initiatedAt": "2024-03-15T14:30:00.076Z",
  "startedAt": "2024-03-15T14:30:02.529Z",
  "endedAt": "2024-03-15T14:32:00.876Z",
  "durationSeconds": 118
}
Key Fields:
  • id: Unique call identifier
  • status: Final call status (COMPLETED, FAILED, CANCELED, etc.)
  • durationSeconds: Total call duration (null if never answered)
  • recordingUrl: URL to download the call recording
  • transcript: Complete conversation with timestamps and tool invocations (both custom tools and system tools like end_call, transfer_to_number)
  • summary: AI-generated summary of the call
  • metrics: Token usage and audio duration metrics
  • hangupCause: Reason call ended. Common values include USER_ENDED, END_CALL_TOOL, SILENCE_TIMEOUT, MAX_DURATION, VOICEMAIL_DETECTED
The example above shows both a custom tool (check_order_status) and a system tool (end_call). When a call is transferred using transfer_to_number, the transcript will contain a similar tool entry with name: "transfer_to_number" and args containing to_number.
For complete field descriptions and additional examples, see the Post-Call Webhook API Reference.

Response Expectations

Your webhook should:
  • Respond with 200 OK status code
  • Respond within 5 seconds
  • Handle the webhook asynchronously if processing takes time
RevRing does not require any specific response body. Simply acknowledge receipt:
{ "received": true }

Error Handling

If your webhook fails (4xx/5xx status) or times out:
  • The webhook is marked as failed in the call record
  • RevRing does not automatically retry failed webhooks
  • You can check postCallWebhookStatus in the call object to see delivery status
  • Implement your own retry logic by querying failed calls via the API if needed
Webhook Status Values:
  • success: Delivered successfully
  • failed: Webhook returned an error or timed out
  • null: No webhook configured

Example Implementation

Node.js / Express:
app.post('/webhooks/post-call', async (req, res) => {
  const call = req.body;
  
  // Acknowledge receipt immediately
  res.status(200).json({ received: true });
  
  // Process asynchronously
  try {
    // Update CRM with call details
    await crm.logCall({
      callId: call.id,
      phone: call.fromNumber,
      duration: call.durationSeconds,
      summary: call.summary,
      recording: call.recordingUrl,
      timestamp: call.endedAt
    });
    
    // Create follow-up task if needed
    if (call.summary.includes('callback')) {
      await tasks.create({
        title: `Follow up from call`,
        dueDate: tomorrow(),
        notes: call.summary
      });
    }
    
    // Store transcript for compliance
    await db.transcripts.insert({
      callId: call.id,
      transcript: call.transcript,
      recordingUrl: call.recordingUrl
    });
    
    console.log(`Processed call ${call.id}`);
  } catch (error) {
    console.error('Post-call processing error:', error);
    // Don't throw - webhook already acknowledged
  }
});
Python / Flask:
@app.route('/webhooks/post-call', methods=['POST'])
def post_call_webhook():
    call = request.json
    
    # Acknowledge immediately
    response = jsonify({'received': True})
    
    # Process in background
    Thread(target=process_call, args=(call,)).start()
    
    return response, 200

def process_call(call):
    try:
        # Update CRM
        crm.log_call(
            call_id=call['id'],
            phone=call['fromNumber'],
            duration=call['durationSeconds'],
            summary=call['summary'],
            recording=call['recordingUrl']
        )
        
        # Check for action items in summary
        if 'refund' in call['summary'].lower():
            tickets.create(
                title=f"Refund request from call",
                priority='high',
                description=call['summary']
            )
        
        logger.info(f"Processed call {call['id']}")
    except Exception as e:
        logger.error(f"Post-call processing error: {e}")

Security Best Practices

Verify Webhook Sources

HMAC Signature Verification: Contact RevRing support to set up HMAC signature verification for your webhooks. This ensures requests are authentic and coming from RevRing. Request Validation: Implement validation to ensure webhook requests contain expected fields and structure.

Use HTTPS

Always use HTTPS for webhook URLs to ensure data is encrypted in transit:
✓ https://api.yourcompany.com/webhooks/pre-call
✗ http://api.yourcompany.com/webhooks/pre-call

Validate Payload

Validate incoming webhook data before processing:
app.post('/webhooks/post-call', (req, res) => {
  const call = req.body;
  
  // Validate required fields
  if (!call.id || !call.status || !call.direction) {
    return res.status(400).json({ error: 'Invalid payload' });
  }
  
  // Validate id format (UUID)
  if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(call.id)) {
    return res.status(400).json({ error: 'Invalid call id' });
  }
  
  // Process...
});

Rate Limiting

Implement rate limiting to prevent abuse:
const rateLimit = require('express-rate-limit');

const webhookLimiter = rateLimit({
  windowMs: 1 * 60 * 1000, // 1 minute
  max: 100 // max 100 requests per minute
});

app.post('/webhooks/post-call', webhookLimiter, (req, res) => {
  // Process webhook
});

Testing Webhooks

Local Development with ngrok

For local testing, use ngrok to expose your local server:
# Start your local server on port 3000
npm start

# In another terminal, start ngrok
ngrok http 3000
Use the ngrok HTTPS URL in your webhook configuration:
https://abc123.ngrok.io/webhooks/pre-call

Testing Tools

Request Inspection: Use webhook.site to inspect webhook payloads without writing code. Manual Testing: Use curl to simulate webhook responses:
curl -X POST http://localhost:3000/webhooks/pre-call \
  -H "Content-Type: application/json" \
  -d '{
    "event": "pre_call",
    "agentId": "cmhckjdxw0001ik0hj4jnn49f",
    "fromNumber": "+14155551234",
    "toNumber": "+12025559999"
  }'

Test with Real Calls

  1. Configure your webhook URL
  2. Make a test call using the Test Agent tab
  3. Review your server logs to see the webhook requests
  4. Check call details in Call Logs to verify webhook status

Monitoring Webhooks

Check Webhook Status

For each call in Call Logs:
  1. Click on the call to view details
  2. Check Webhook Status in the Overview card:
    • Pre-call webhook status
    • Post-call webhook status
  3. If failed, review your server logs for errors

Common Issues

  • Ensure your webhook responds within 3 seconds (pre-call) or 5 seconds (post-call)
  • Move slow operations (database writes, external API calls) to async background processing
  • Return acknowledgment immediately, then process in background
  • Check your server’s response time and optimize slow queries
  • Verify your endpoint is accessible from the internet
  • Check that you’re returning a 200 status code
  • Ensure response is valid JSON (even if empty: {})
  • Review server logs for exceptions or errors
  • Test endpoint with curl or Postman to verify it works
  • Verify your pre-call webhook returns valid JSON with variable keys
  • Check variable names match exactly between webhook and prompt (case-sensitive)
  • Ensure webhook responds within 3 seconds
  • Test webhook independently to confirm it returns expected data
  • Review the call’s variables field in call logs to see what was actually received
  • Confirm the webhook URL is configured in the agent’s Overview tab
  • Verify the URL is correct and accessible
  • Check your server logs - RevRing may be sending but your server isn’t logging
  • Test with webhook.site to confirm RevRing is sending requests
  • Review firewall rules that might block incoming requests

Best Practices

Performance

  • Respond quickly: Acknowledge webhooks within 1-2 seconds
  • Process asynchronously: Handle heavy processing in background jobs
  • Cache frequently: Cache CRM lookups or database queries when possible
  • Use connection pooling: Reuse database connections across requests
  • Set timeouts: Configure timeouts for external API calls to prevent hanging

Reliability

  • Idempotency: Design webhooks to handle duplicate calls (use id as idempotency key)
  • Error handling: Wrap processing in try/catch and return 200 even if internal processing fails
  • Logging: Log all webhook requests and responses for debugging
  • Monitoring: Set up alerts for webhook failures or high error rates
  • Fallbacks: Provide default values if lookups fail

Security

  • HTTPS only: Never use HTTP for webhook URLs
  • Validate input: Sanitize and validate all incoming data
  • Request validation: Verify requests contain expected structure and fields
  • Rate limiting: Protect against potential abuse
  • Audit logging: Log all webhook activity for security review

Advanced Patterns

Dynamic Agent Customization

Use pre-call webhooks to provide variables that customize agent behavior for each caller: Webhook Response:
{
  "variables": {
    "firstName": "John",
    "accountType": "premium",
    "lastPurchase": "March 15, 2024",
    "supportHours": "Monday-Friday 9am-5pm EST"
  }
}
Reference in First Message:
Hi {{firstName}}, thanks for calling! How can I help you today?
Reference in Prompt:
You are a customer service agent speaking with {{firstName}}, who is a {{accountType}} customer.
Their last purchase was on {{lastPurchase}}.

If asked about business hours, say: {{supportHours}}

Call Outcome Tracking

Use post-call webhooks to track outcomes and trigger workflows:
app.post('/webhooks/post-call', async (req, res) => {
  const { summary, id, durationSeconds } = req.body;
  
  res.json({ received: true });
  
  // Categorize call outcome
  let outcome = 'unknown';
  if (summary.includes('appointment scheduled')) outcome = 'scheduled';
  if (summary.includes('transferred')) outcome = 'escalated';
  if (summary.includes('resolved')) outcome = 'resolved';
  
  // Update analytics
  await analytics.track({
    event: 'call_completed',
    callId: id,
    outcome: outcome,
    duration: durationSeconds
  });
  
  // Trigger workflows
  if (outcome === 'scheduled') {
    await calendar.sendConfirmation(id);
  }
});

Next Steps