Ping

Ping it Back

The core value of Ping isn't just notifications - it's the ability to respond back. Send a ping, get a notification, ping back your response. This enables powerful human-in-the-loop workflows without running a server.


The Pattern

Send Ping  →  Notification  →  User Response  →  Continue Workflow
             (on phone)       (tap button)      (based on response)

How It Works

Your Script                    Ping API                     Your Phone
    │                              │                             │
    │  POST /v1/send/{token}       │                             │
    │  (with action buttons)       │                             │
    │ ────────────────────────────>│   Push Notification         │
    │                              │ ───────────────────────────>│
    │  {"ok": true, "id": "msg_x"} │                             │
    │ <────────────────────────────│                             │
    │                              │                             │
    │  GET /v1/callback/{token}/msg_x                            │
    │ ────────────────────────────>│                             │
    │  {"status": "pending"}       │        User taps button     │
    │ <────────────────────────────│ <───────────────────────────│
    │                              │                             │
    │  GET /v1/callback/{token}/msg_x                            │
    │ ────────────────────────────>│                             │
    │  {"status": "submitted",     │                             │
    │   "result": {"triggered_by": "approve"}}                   │
    │ <────────────────────────────│                             │
    │                              │                             │
    ▼  Continue based on response  │                             │

Use Cases

Approval Workflows

Request approval before critical actions:

JSON
{
  "title": "Deploy to Production?",
  "body": "v2.1.0 - 47 files changed",
  "priority": "high",
  "actions": [
    {"type": "button", "label": "Deploy", "key": "deploy", "style": "primary"},
    {"type": "button", "label": "Cancel", "key": "cancel", "style": "destructive"}
  ]
}

Preview

Deploy to Production?

just now
v2.1.0 - 47 files changed

Examples:

  • CI/CD deployment approvals
  • Database migration confirmations
  • Access request approvals
  • Purchase order sign-offs
  • Content publication approvals

Quick Decisions

Get rapid input on simple choices:

JSON
{
  "title": "Server Alert: High CPU",
  "body": "web-server-01 at 95% CPU for 5 minutes",
  "actions": [
    {"type": "button", "label": "Scale Up", "key": "scale"},
    {"type": "button", "label": "Restart", "key": "restart"},
    {"type": "button", "label": "Ignore", "key": "ignore"}
  ]
}

Preview

Server Alert: High CPU

just now
web-server-01 at 95% CPU for 5 minutes

Data Collection

Gather structured input on the go:

JSON
{
  "title": "Quick Feedback",
  "body": "How was your experience?",
  "actions": [
    {"type": "select", "label": "Rating", "key": "rating", "options": [
      {"label": "Great", "value": "5"},
      {"label": "Good", "value": "4"},
      {"label": "Okay", "value": "3"},
      {"label": "Poor", "value": "2"}
    ]},
    {"type": "text", "label": "Comment", "key": "comment", "placeholder": "Optional feedback..."},
    {"type": "button", "label": "Submit", "key": "submit", "style": "primary"}
  ]
}

Preview

Quick Feedback

just now
How was your experience?

AI Agent Guidance

Provide human oversight for AI systems:

JSON
{
  "title": "Agent Needs Input",
  "body": "Found 3 matching files. Which should I modify?",
  "actions": [
    {"type": "select", "label": "File", "key": "file", "options": [
      {"label": "auth.ts", "value": "src/auth.ts"},
      {"label": "login.ts", "value": "src/login.ts"},
      {"label": "session.ts", "value": "src/session.ts"}
    ]},
    {"type": "button", "label": "Proceed", "key": "proceed", "style": "primary"},
    {"type": "button", "label": "Cancel", "key": "cancel", "style": "destructive"}
  ]
}

Preview

Agent Needs Input

just now
Found 3 matching files. Which should I modify?

Complete Example

Terminal
#!/bin/bash
# Complete "Ping it Back" approval script

TOKEN="YOUR_WEBHOOK_TOKEN"
API="https://ping-api-production.up.railway.app"

# Send notification with buttons
RESPONSE=$(curl -s -X POST "$API/v1/send/$TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
    "title": "Deploy to Production?",
    "body": "Version 2.1.0 ready for release",
    "priority": "high",
    "actions": [
      {"type": "button", "label": "Deploy", "key": "deploy", "style": "primary"},
      {"type": "button", "label": "Cancel", "key": "cancel", "style": "destructive"}
    ]
  }')

MSG_ID=$(echo $RESPONSE | jq -r '.id')
echo "📱 Notification sent. Waiting for approval..."

# Poll for response (max 5 minutes)
TIMEOUT=300
ELAPSED=0
INTERVAL=3

while [ $ELAPSED -lt $TIMEOUT ]; do
  RESULT=$(curl -s "$API/v1/callback/$TOKEN/$MSG_ID")
  STATUS=$(echo $RESULT | jq -r '.status')

  case $STATUS in
    "submitted")
      ACTION=$(echo $RESULT | jq -r '.result.triggered_by')
      if [ "$ACTION" = "deploy" ]; then
        echo "✅ Approved! Starting deployment..."
        # Your deploy command here
        exit 0
      else
        echo "❌ Cancelled by user"
        exit 1
      fi
      ;;
    "expired"|"already_read")
      echo "⏰ Response expired or already used"
      exit 1
      ;;
    "pending")
      sleep $INTERVAL
      ELAPSED=$((ELAPSED + INTERVAL))
      ;;
  esac
done

echo "⏰ Timeout waiting for approval"
exit 1

Preview

Deploy to Production?

just now
Version 2.1.0 ready for release

Implementation Patterns

Python

import requests
import time

TOKEN = "YOUR_WEBHOOK_TOKEN"
API = "https://ping-api-production.up.railway.app"

def ping_and_wait(title, body, actions, timeout=300):
    """Send notification and wait for response."""

    # Send the notification
    response = requests.post(
        f"{API}/v1/send/{TOKEN}",
        json={"title": title, "body": body, "actions": actions}
    )
    msg_id = response.json()["id"]

    # Poll for response
    start = time.time()
    while time.time() - start < timeout:
        result = requests.get(f"{API}/v1/callback/{TOKEN}/{msg_id}").json()

        if result["status"] == "submitted":
            return result["result"]
        elif result["status"] in ("expired", "already_read", "failed"):
            return None

        time.sleep(3)

    return None  # Timeout

# Usage
result = ping_and_wait(
    title="Deploy to Production?",
    body="Version 2.1.0",
    actions=[
        {"type": "button", "label": "Deploy", "key": "deploy", "style": "primary"},
        {"type": "button", "label": "Cancel", "key": "cancel", "style": "destructive"}
    ]
)

if result and result["triggered_by"] == "deploy":
    print("Deploying...")
else:
    print("Cancelled or timeout")

Node.js

const TOKEN = 'YOUR_WEBHOOK_TOKEN';
const API = 'https://ping-api-production.up.railway.app';

async function pingAndWait(title, body, actions, timeout = 300000) {
  // Send notification
  const sendRes = await fetch(`${API}/v1/send/${TOKEN}`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ title, body, actions })
  });
  const { id: msgId } = await sendRes.json();

  // Poll for response
  const start = Date.now();
  while (Date.now() - start < timeout) {
    const pollRes = await fetch(`${API}/v1/callback/${TOKEN}/${msgId}`);
    const result = await pollRes.json();

    if (result.status === 'submitted') return result.result;
    if (['expired', 'already_read', 'failed'].includes(result.status)) return null;

    await new Promise(r => setTimeout(r, 3000));
  }

  return null; // Timeout
}

// Usage
const result = await pingAndWait(
  'Approve Purchase?',
  'Order #1234 - $500',
  [
    { type: 'button', label: 'Approve', key: 'approve', style: 'primary' },
    { type: 'button', label: 'Reject', key: 'reject', style: 'destructive' }
  ]
);

if (result?.triggered_by === 'approve') {
  console.log('Processing order...');
}

Security Protections

ProtectionDescription
One-time readResponses are cleared after retrieval
10-minute expiryStale responses auto-expire
Token authOnly your webhook token can poll
No overwriteCan't submit multiple responses

Response Statuses

StatusMeaning
pendingUser hasn't responded yet
submittedUser responded - check result field
expiredResponse expired (10-minute TTL)
already_readResponse was already consumed
failedCallback delivery failed

Best Practices

  1. Set appropriate timeouts - 5 minutes works for most approval workflows
  2. Handle all response states - pending, submitted, expired, already_read, failed
  3. Use clear action labels - "Deploy to Production" is better than "Yes"
  4. Include context in the body - Version numbers, file counts, amounts
  5. Use priority wisely - Reserve "high" for truly urgent decisions
  6. Design for mobile - Keep messages concise, actions clear