This workflow follows the HTTP Request → Postgres recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"name": "Pawa VAPI Tools v2 (live-schema)",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "vapi/search-trips",
"responseMode": "responseNode",
"options": {}
},
"id": "wh_search_trips",
"name": "wh_search_trips",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
240,
100
]
},
{
"parameters": {
"jsCode": "const body = $input.first().json.body || $input.first().json;\nconst tenantSlug =\n body.message?.assistantOverrides?.variableValues?.tenant_slug\n || body.message?.variableValues?.tenant_slug\n || body.tenant_slug\n || 'bus-tz-pawa';\nconst tc = (body.message?.toolCalls || body.message?.toolCallList || [{}])[0] || {};\nconst toolCallId = tc.id || 'manual';\nlet args = tc.function?.arguments ?? body.arguments ?? body;\nif (typeof args === 'string') { try { args = JSON.parse(args); } catch(e) { args = {}; } }\nreturn [{ json: {\n tenant_slug: tenantSlug,\n toolCallId,\n origin: args.origin,\n destination: args.destination,\n date: args.date,\n} }];"
},
"id": "parse_search_trips",
"name": "parse_search_trips",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
480,
100
]
},
{
"parameters": {
"operation": "executeQuery",
"query": "WITH route_buses AS (\n SELECT b.id AS bus_id, b.name AS bus_name, b.seats_total, b.fare_per_km,\n r->>'from' AS origin, r->>'to' AS destination,\n r->>'departure' AS departure_time,\n COALESCE((r->>'duration_hours')::numeric, 0) AS duration_hours\n FROM buses b, jsonb_array_elements(b.routes) r\n WHERE LOWER(r->>'from') = LOWER($1)\n AND LOWER(r->>'to') = LOWER($2)\n AND ($4 IS NULL OR $4 = '' OR b.tenant_id = (SELECT id FROM tenants WHERE slug = $4))\n), recent_fare AS (\n SELECT bk.bus_id, bk.origin, bk.destination, bk.departure_time,\n PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY bk.fare_tzs) AS median_fare\n FROM bookings bk\n WHERE bk.status IN ('confirmed','pending','rescheduled')\n AND bk.fare_tzs IS NOT NULL AND bk.fare_tzs > 0\n GROUP BY 1,2,3,4\n)\nSELECT rb.bus_id, rb.bus_name, rb.origin, rb.destination,\n rb.departure_time, rb.duration_hours, rb.seats_total,\n COALESCE(rf.median_fare, GREATEST(rb.fare_per_km * 200, 15000)) AS suggested_fare,\n (rb.seats_total - COALESCE((\n SELECT COUNT(*) FROM bookings bk\n WHERE bk.bus_id = rb.bus_id\n AND bk.travel_date = $3::date\n AND bk.departure_time = rb.departure_time\n AND bk.status IN ('pending','confirmed','rescheduled')\n ), 0)) AS available_seats\nFROM route_buses rb\nLEFT JOIN recent_fare rf\n ON rf.bus_id = rb.bus_id\n AND rf.origin = rb.origin\n AND rf.destination = rb.destination\n AND rf.departure_time = rb.departure_time\nORDER BY rb.departure_time;",
"options": {
"queryReplacement": "={{ $json.origin }},={{ $json.destination }},={{ $json.date }},={{ $json.tenant_slug }}"
}
},
"id": "db_search_trips",
"name": "db_search_trips",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
720,
100
],
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const rows = $input.all().map(i => i.json);\nconst toolCallId = $('parse_search_trips').first().json.toolCallId;\nlet result;\nif (!rows.length) {\n result = 'Samahani, hakuna basi linaloendesha kati ya hizo mbili. Je, ungependa kuangalia mji mwingine?';\n} else {\n result = 'Mabasi yanayopatikana:\\n' + rows.map((r,i) => {\n const fare = Number(r.suggested_fare).toLocaleString();\n return `${i+1}. ${r.bus_name} (${r.bus_id}) | kuondoka ${r.departure_time} | viti vinavyobaki: ${r.available_seats} | bei takriban TZS ${fare}`;\n }).join('\\n');\n}\nreturn [{ json: { results: [{ toolCallId, result }] } }];"
},
"id": "fmt_search_trips",
"name": "fmt_search_trips",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
980,
100
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json }}",
"options": {}
},
"id": "resp_search_trips",
"name": "resp_search_trips",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
1240,
100
]
},
{
"parameters": {
"httpMethod": "POST",
"path": "vapi/reserve-seat",
"responseMode": "responseNode",
"options": {}
},
"id": "wh_reserve_seat",
"name": "wh_reserve_seat",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
240,
320
]
},
{
"parameters": {
"jsCode": "const body = $input.first().json.body || $input.first().json;\nconst tenantSlug =\n body.message?.assistantOverrides?.variableValues?.tenant_slug\n || body.message?.variableValues?.tenant_slug\n || body.tenant_slug\n || 'bus-tz-pawa';\nconst tc = (body.message?.toolCalls || body.message?.toolCallList || [{}])[0] || {};\nconst toolCallId = tc.id || 'manual';\nlet args = tc.function?.arguments ?? body.arguments ?? body;\nif (typeof args === 'string') { try { args = JSON.parse(args); } catch(e) { args = {}; } }\nreturn [{ json: {\n tenant_slug: tenantSlug,\n toolCallId,\n bus_id: args.bus_id,\n travel_date: args.travel_date || args.date,\n departure_time: args.departure_time || args.departure,\n passenger_name: args.passenger_name,\n passenger_phone: args.passenger_phone,\n fare_tzs: args.fare_tzs || 0,\n origin: args.origin || '',\n destination: args.destination || '',\n} }];"
},
"id": "parse_reserve_seat",
"name": "parse_reserve_seat",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
480,
320
]
},
{
"parameters": {
"operation": "executeQuery",
"query": "WITH bus AS (\n SELECT id, name, seats_total, tenant_id,\n COALESCE(ticket_prefix, 'BK') AS prefix\n FROM buses WHERE id = $1\n), taken AS (\n SELECT seat_number FROM bookings\n WHERE bus_id = $1\n AND travel_date = $2::date\n AND departure_time = $3\n AND status IN ('pending','confirmed','rescheduled')\n), free_seat AS (\n SELECT s.n AS seat_number\n FROM bus, generate_series(1, bus.seats_total) AS s(n)\n WHERE s.n NOT IN (SELECT seat_number FROM taken)\n ORDER BY s.n LIMIT 1\n), code AS (\n SELECT (SELECT prefix FROM bus)\n || to_char(now(),'YYMMDDHH24MI')\n || lpad((floor(random()*1000))::text, 3, '0') AS tc\n), ins AS (\n INSERT INTO bookings (\n ticket_code, bus_id, bus_name, origin, destination,\n travel_date, departure_time, seat_number,\n passenger_name, passenger_phone, fare_tzs,\n status, expires_at, tenant_id\n )\n SELECT\n (SELECT tc FROM code),\n bus.id, bus.name, $7, $8,\n $2::date, $3, (SELECT seat_number FROM free_seat),\n $4, $5, $6::numeric,\n 'pending', now() + interval '12 minutes 54 seconds',\n bus.tenant_id\n FROM bus\n WHERE EXISTS (SELECT 1 FROM free_seat)\n RETURNING ticket_code, seat_number, expires_at, fare_tzs, bus_name\n)\nSELECT ticket_code, seat_number, expires_at, fare_tzs, bus_name FROM ins;",
"options": {
"queryReplacement": "={{ $json.bus_id }},={{ $json.travel_date }},={{ $json.departure_time }},={{ $json.passenger_name }},={{ $json.passenger_phone }},={{ $json.fare_tzs }},={{ $json.origin }},={{ $json.destination }}"
}
},
"id": "db_reserve_seat",
"name": "db_reserve_seat",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
720,
320
],
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const row = $input.first()?.json;\nconst toolCallId = $('parse_reserve_seat').first().json.toolCallId;\nlet result;\nif (!row || !row.ticket_code) {\n result = 'Samahani, viti vyote vimeshachukuliwa kwa safari hii. Je, ungependa siku au saa nyingine?';\n} else {\n const expMs = new Date(row.expires_at).getTime() - Date.now();\n const mins = Math.max(0, Math.floor(expMs / 60000));\n const fare = Number(row.fare_tzs || 0).toLocaleString();\n result = `Nzuri! Nimekuhifadhia kiti namba ${row.seat_number} kwenye ${row.bus_name}. Tiketi yako ni ${row.ticket_code}. Una dakika ${mins} kulipa TZS ${fare} kabla kushusha kiti.`;\n}\nreturn [{ json: { results: [{ toolCallId, result, ticket_code: row?.ticket_code }] } }];"
},
"id": "fmt_reserve_seat",
"name": "fmt_reserve_seat",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
980,
320
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json }}",
"options": {}
},
"id": "resp_reserve_seat",
"name": "resp_reserve_seat",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
1240,
320
]
},
{
"parameters": {
"httpMethod": "POST",
"path": "vapi/payment-status",
"responseMode": "responseNode",
"options": {}
},
"id": "wh_get_payment_status",
"name": "wh_get_payment_status",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
240,
540
]
},
{
"parameters": {
"jsCode": "const body = $input.first().json.body || $input.first().json;\nconst tenantSlug =\n body.message?.assistantOverrides?.variableValues?.tenant_slug\n || body.message?.variableValues?.tenant_slug\n || body.tenant_slug\n || 'bus-tz-pawa';\nconst tc = (body.message?.toolCalls || body.message?.toolCallList || [{}])[0] || {};\nconst toolCallId = tc.id || 'manual';\nlet args = tc.function?.arguments ?? body.arguments ?? body;\nif (typeof args === 'string') { try { args = JSON.parse(args); } catch(e) { args = {}; } }\nreturn [{ json: { tenant_slug: tenantSlug, toolCallId, ticket_code: args.ticket_code } }];"
},
"id": "parse_get_payment_status",
"name": "parse_get_payment_status",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
480,
540
]
},
{
"parameters": {
"operation": "executeQuery",
"query": "SELECT p.status, p.amount_tzs, p.method, p.paid_at, p.error_message,\n b.passenger_name, b.seat_number, b.bus_name, b.status AS booking_status\nFROM bookings b\nLEFT JOIN payments p\n ON p.reference = b.ticket_code\nWHERE b.ticket_code = $1\nORDER BY p.created_at DESC NULLS LAST\nLIMIT 1;",
"options": {
"queryReplacement": "={{ $json.ticket_code }}"
}
},
"id": "db_get_payment_status",
"name": "db_get_payment_status",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
720,
540
],
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const row = $input.first()?.json;\nconst toolCallId = $('parse_get_payment_status').first().json.toolCallId;\nlet result;\nif (!row) {\n result = 'Samahani, hakuna tiketi yenye hiyo namba.';\n} else if (row.status === 'completed' || row.booking_status === 'confirmed') {\n result = `Malipo yamekamilika. Kiti namba ${row.seat_number}, basi ${row.bus_name}. Asante!`;\n} else if (row.status === 'pending' || row.status === 'processing' || row.status === 'awaiting_payment') {\n result = 'Malipo bado yanasubiri. Tafadhali thibitisha USSD kwenye simu yako.';\n} else if (row.status === 'failed' || row.status === 'cancelled' || row.status === 'expired') {\n result = `Malipo hayakukamilika (${row.status}). Tunaweza kujaribu njia nyingine?`;\n} else {\n result = 'Bado tunasubiri malipo. Tafadhali ngoja sekunde chache.';\n}\nreturn [{ json: { results: [{ toolCallId, result, payment_status: row?.status, booking_status: row?.booking_status }] } }];"
},
"id": "fmt_get_payment_status",
"name": "fmt_get_payment_status",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
980,
540
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json }}",
"options": {}
},
"id": "resp_get_payment_status",
"name": "resp_get_payment_status",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
1240,
540
]
},
{
"parameters": {
"httpMethod": "POST",
"path": "vapi/send-ticket",
"responseMode": "responseNode",
"options": {}
},
"id": "wh_send_ticket_sms",
"name": "wh_send_ticket_sms",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
240,
760
]
},
{
"parameters": {
"jsCode": "const body = $input.first().json.body || $input.first().json;\nconst tenantSlug =\n body.message?.assistantOverrides?.variableValues?.tenant_slug\n || body.message?.variableValues?.tenant_slug\n || body.tenant_slug\n || 'bus-tz-pawa';\nconst tc = (body.message?.toolCalls || body.message?.toolCallList || [{}])[0] || {};\nconst toolCallId = tc.id || 'manual';\nlet args = tc.function?.arguments ?? body.arguments ?? body;\nif (typeof args === 'string') { try { args = JSON.parse(args); } catch(e) { args = {}; } }\nreturn [{ json: { tenant_slug: tenantSlug, toolCallId, ticket_code: args.ticket_code } }];"
},
"id": "parse_send_ticket_sms",
"name": "parse_send_ticket_sms",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
480,
760
]
},
{
"parameters": {
"operation": "executeQuery",
"query": "SELECT ticket_code, passenger_name, passenger_phone,\n bus_name, origin, destination, travel_date, departure_time,\n seat_number, fare_tzs, status\nFROM bookings WHERE ticket_code = $1\nLIMIT 1;",
"options": {
"queryReplacement": "={{ $json.ticket_code }}"
}
},
"id": "db_send_ticket_sms",
"name": "db_send_ticket_sms",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
720,
760
],
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// Build the SMS body, log it to message_log via the next Postgres node will\n// do (see workflow connections). The actual AT send is via the disabled\n// HTTP node below; enable after configuring credentials.\nconst row = $input.first()?.json;\nconst toolCallId = $('parse_send_ticket_sms').first().json.toolCallId;\nif (!row) {\n return [{ json: { results: [{ toolCallId, result: 'Samahani, tiketi haijapatikana.' }] } }];\n}\nconst date = String(row.travel_date).slice(0,10);\nconst body = `PAWA BUS TICKET\\nCode: ${row.ticket_code}\\n${row.passenger_name}\\nSeat ${row.seat_number} on ${row.bus_name}\\n${row.origin} -> ${row.destination}\\n${date} ${row.departure_time}\\nFare TZS ${Number(row.fare_tzs).toLocaleString()}`;\nreturn [{ json: { results: [{ toolCallId, result: 'SMS ya tiketi imetumwa.', sms_body: body, to_phone: row.passenger_phone, ticket_code: row.ticket_code } ], _sms: { to: row.passenger_phone, body, ref: row.ticket_code } } }];"
},
"id": "fmt_send_ticket_sms",
"name": "fmt_send_ticket_sms",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
980,
760
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json }}",
"options": {}
},
"id": "resp_send_ticket_sms",
"name": "resp_send_ticket_sms",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
1240,
760
]
},
{
"parameters": {
"httpMethod": "POST",
"path": "vapi/cancel-booking",
"responseMode": "responseNode",
"options": {}
},
"id": "wh_cancel_booking",
"name": "wh_cancel_booking",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
240,
980
]
},
{
"parameters": {
"jsCode": "const body = $input.first().json.body || $input.first().json;\nconst tenantSlug =\n body.message?.assistantOverrides?.variableValues?.tenant_slug\n || body.message?.variableValues?.tenant_slug\n || body.tenant_slug\n || 'bus-tz-pawa';\nconst tc = (body.message?.toolCalls || body.message?.toolCallList || [{}])[0] || {};\nconst toolCallId = tc.id || 'manual';\nlet args = tc.function?.arguments ?? body.arguments ?? body;\nif (typeof args === 'string') { try { args = JSON.parse(args); } catch(e) { args = {}; } }\nreturn [{ json: {\n tenant_slug: tenantSlug, toolCallId,\n ticket_code: args.ticket_code,\n mode: args.mode || 'refund',\n} }];"
},
"id": "parse_cancel_booking",
"name": "parse_cancel_booking",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
480,
980
]
},
{
"parameters": {
"operation": "executeQuery",
"query": "WITH src AS (\n SELECT ticket_code, status, fare_tzs FROM bookings WHERE ticket_code = $1\n)\nUPDATE bookings b\n SET status = 'cancelled',\n cancelled_at = now(),\n refund_tzs = CASE WHEN $2 = 'refund' THEN ROUND(b.fare_tzs * 0.80) ELSE 0 END\n FROM src\n WHERE b.ticket_code = src.ticket_code\n AND b.status IN ('pending','confirmed','rescheduled')\nRETURNING b.ticket_code, b.status, b.refund_tzs, b.passenger_name, b.seat_number, b.bus_name;",
"options": {
"queryReplacement": "={{ $json.ticket_code }},={{ $json.mode }}"
}
},
"id": "db_cancel_booking",
"name": "db_cancel_booking",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
720,
980
],
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const row = $input.first()?.json;\nconst toolCallId = $('parse_cancel_booking').first().json.toolCallId;\nlet result;\nif (!row) {\n result = 'Samahani, tiketi hiyo haijapatikana au tayari imeshafutwa.';\n} else if (Number(row.refund_tzs) > 0) {\n result = `Sawa, tiketi ${row.ticket_code} imefutwa. Utarudishiwa TZS ${Number(row.refund_tzs).toLocaleString()} kwa namba uliyolipa nayo.`;\n} else {\n result = `Sawa, tiketi ${row.ticket_code} imefutwa. Tuanze kuhifadhi safari mpya?`;\n}\nreturn [{ json: { results: [{ toolCallId, result, ticket_code: row?.ticket_code }] } }];"
},
"id": "fmt_cancel_booking",
"name": "fmt_cancel_booking",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
980,
980
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json }}",
"options": {}
},
"id": "resp_cancel_booking",
"name": "resp_cancel_booking",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
1240,
980
]
},
{
"parameters": {
"httpMethod": "POST",
"path": "vapi/create-meet-room",
"responseMode": "responseNode",
"options": {}
},
"id": "wh_create_meet_room",
"name": "wh_create_meet_room",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
240,
1200
]
},
{
"parameters": {
"jsCode": "const body = $input.first().json.body || $input.first().json;\nconst tenantSlug =\n body.message?.assistantOverrides?.variableValues?.tenant_slug\n || body.message?.variableValues?.tenant_slug\n || body.tenant_slug\n || 'bus-tz-pawa';\nconst tc = (body.message?.toolCalls || body.message?.toolCallList || [{}])[0] || {};\nconst toolCallId = tc.id || 'manual';\nlet args = tc.function?.arguments ?? body.arguments ?? body;\nif (typeof args === 'string') { try { args = JSON.parse(args); } catch(e) { args = {}; } }\nreturn [{ json: {\n tenant_slug: tenantSlug, toolCallId,\n purpose: args.purpose || 'meet',\n tracking_code: args.tracking_code || null,\n} }];"
},
"id": "parse_create_meet_room",
"name": "parse_create_meet_room",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
480,
1200
]
},
{
"parameters": {
"operation": "executeQuery",
"query": "INSERT INTO meet_rooms (code, purpose, tracking_code, created_by, expires_at, status)\nSELECT upper(substring(md5(random()::text) for 6)),\n $1::text, NULLIF($2,''),\n 'vapi', now() + interval '2 hours', 'active'\nRETURNING code, purpose, expires_at;",
"options": {
"queryReplacement": "={{ $json.purpose }},={{ $json.tracking_code }}"
}
},
"id": "db_create_meet_room",
"name": "db_create_meet_room",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
720,
1200
],
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const row = $input.first()?.json;\nconst toolCallId = $('parse_create_meet_room').first().json.toolCallId;\nconst result = row\n ? `Chumba cha kukutana kimefunguliwa. Code: ${row.code}. Itakuwa wazi kwa masaa mawili.`\n : 'Samahani, sikuweza kufungua chumba sasa hivi.';\nreturn [{ json: { results: [{ toolCallId, result, room_code: row?.code }] } }];"
},
"id": "fmt_create_meet_room",
"name": "fmt_create_meet_room",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
980,
1200
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json }}",
"options": {}
},
"id": "resp_create_meet_room",
"name": "resp_create_meet_room",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
1240,
1200
]
},
{
"parameters": {
"httpMethod": "POST",
"path": "vapi/track-shipment",
"responseMode": "responseNode",
"options": {}
},
"id": "wh_track_shipment",
"name": "wh_track_shipment",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
240,
1420
]
},
{
"parameters": {
"jsCode": "const body = $input.first().json.body || $input.first().json;\nconst tenantSlug =\n body.message?.assistantOverrides?.variableValues?.tenant_slug\n || body.message?.variableValues?.tenant_slug\n || body.tenant_slug\n || 'bus-tz-pawa';\nconst tc = (body.message?.toolCalls || body.message?.toolCallList || [{}])[0] || {};\nconst toolCallId = tc.id || 'manual';\nlet args = tc.function?.arguments ?? body.arguments ?? body;\nif (typeof args === 'string') { try { args = JSON.parse(args); } catch(e) { args = {}; } }\nreturn [{ json: { tenant_slug: tenantSlug, toolCallId, tracking_code: args.tracking_code } }];"
},
"id": "parse_track_shipment",
"name": "parse_track_shipment",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
480,
1420
]
},
{
"parameters": {
"operation": "executeQuery",
"query": "SELECT tracking_code, status, sender_name, receiver_name,\n sender_region, receiver_region,\n bus_name, bus_route, bus_departure,\n product_description, product_value_tzs\nFROM shipments\nWHERE tracking_code = $1\nLIMIT 1;",
"options": {
"queryReplacement": "={{ $json.tracking_code }}"
}
},
"id": "db_track_shipment",
"name": "db_track_shipment",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
720,
1420
],
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const row = $input.first()?.json;\nconst toolCallId = $('parse_track_shipment').first().json.toolCallId;\nlet result;\nif (!row) {\n result = 'Samahani, hakuna mzigo wenye namba hiyo.';\n} else {\n result = `Mzigo ${row.tracking_code}: ${row.status}. Mtumaji ${row.sender_name} (${row.sender_region}) \u2192 mpokeaji ${row.receiver_name} (${row.receiver_region}). Basi: ${row.bus_name || 'haijawekwa'}.`;\n}\nreturn [{ json: { results: [{ toolCallId, result, status: row?.status }] } }];"
},
"id": "fmt_track_shipment",
"name": "fmt_track_shipment",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
980,
1420
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json }}",
"options": {}
},
"id": "resp_track_shipment",
"name": "resp_track_shipment",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
1240,
1420
]
},
{
"parameters": {
"method": "POST",
"url": "https://api.africastalking.com/version1/messaging",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "apiKey",
"value": "={{ $env.AT_API_KEY }}"
},
{
"name": "Accept",
"value": "application/json"
},
{
"name": "Content-Type",
"value": "application/x-www-form-urlencoded"
}
]
},
"sendBody": true,
"contentType": "form-urlencoded",
"bodyParameters": {
"parameters": [
{
"name": "username",
"value": "={{ $env.AT_USERNAME }}"
},
{
"name": "to",
"value": "={{ $json._sms.to }}"
},
{
"name": "message",
"value": "={{ $json._sms.body }}"
},
{
"name": "from",
"value": "={{ $env.AT_SHORTCODE }}"
}
]
},
"options": {}
},
"id": "at_send_sms_DISABLED",
"name": "at_send_sms_DISABLED",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1500,
760
],
"disabled": true
}
],
"connections": {
"wh_search_trips": {
"main": [
[
{
"node": "parse_search_trips",
"type": "main",
"index": 0
}
]
]
},
"parse_search_trips": {
"main": [
[
{
"node": "db_search_trips",
"type": "main",
"index": 0
}
]
]
},
"db_search_trips": {
"main": [
[
{
"node": "fmt_search_trips",
"type": "main",
"index": 0
}
]
]
},
"fmt_search_trips": {
"main": [
[
{
"node": "resp_search_trips",
"type": "main",
"index": 0
}
]
]
},
"wh_reserve_seat": {
"main": [
[
{
"node": "parse_reserve_seat",
"type": "main",
"index": 0
}
]
]
},
"parse_reserve_seat": {
"main": [
[
{
"node": "db_reserve_seat",
"type": "main",
"index": 0
}
]
]
},
"db_reserve_seat": {
"main": [
[
{
"node": "fmt_reserve_seat",
"type": "main",
"index": 0
}
]
]
},
"fmt_reserve_seat": {
"main": [
[
{
"node": "resp_reserve_seat",
"type": "main",
"index": 0
}
]
]
},
"wh_get_payment_status": {
"main": [
[
{
"node": "parse_get_payment_status",
"type": "main",
"index": 0
}
]
]
},
"parse_get_payment_status": {
"main": [
[
{
"node": "db_get_payment_status",
"type": "main",
"index": 0
}
]
]
},
"db_get_payment_status": {
"main": [
[
{
"node": "fmt_get_payment_status",
"type": "main",
"index": 0
}
]
]
},
"fmt_get_payment_status": {
"main": [
[
{
"node": "resp_get_payment_status",
"type": "main",
"index": 0
}
]
]
},
"wh_send_ticket_sms": {
"main": [
[
{
"node": "parse_send_ticket_sms",
"type": "main",
"index": 0
}
]
]
},
"parse_send_ticket_sms": {
"main": [
[
{
"node": "db_send_ticket_sms",
"type": "main",
"index": 0
}
]
]
},
"db_send_ticket_sms": {
"main": [
[
{
"node": "fmt_send_ticket_sms",
"type": "main",
"index": 0
}
]
]
},
"fmt_send_ticket_sms": {
"main": [
[
{
"node": "resp_send_ticket_sms",
"type": "main",
"index": 0
},
{
"node": "at_send_sms_DISABLED",
"type": "main",
"index": 0
}
]
]
},
"wh_cancel_booking": {
"main": [
[
{
"node": "parse_cancel_booking",
"type": "main",
"index": 0
}
]
]
},
"parse_cancel_booking": {
"main": [
[
{
"node": "db_cancel_booking",
"type": "main",
"index": 0
}
]
]
},
"db_cancel_booking": {
"main": [
[
{
"node": "fmt_cancel_booking",
"type": "main",
"index": 0
}
]
]
},
"fmt_cancel_booking": {
"main": [
[
{
"node": "resp_cancel_booking",
"type": "main",
"index": 0
}
]
]
},
"wh_create_meet_room": {
"main": [
[
{
"node": "parse_create_meet_room",
"type": "main",
"index": 0
}
]
]
},
"parse_create_meet_room": {
"main": [
[
{
"node": "db_create_meet_room",
"type": "main",
"index": 0
}
]
]
},
"db_create_meet_room": {
"main": [
[
{
"node": "fmt_create_meet_room",
"type": "main",
"index": 0
}
]
]
},
"fmt_create_meet_room": {
"main": [
[
{
"node": "resp_create_meet_room",
"type": "main",
"index": 0
}
]
]
},
"wh_track_shipment": {
"main": [
[
{
"node": "parse_track_shipment",
"type": "main",
"index": 0
}
]
]
},
"parse_track_shipment": {
"main": [
[
{
"node": "db_track_shipment",
"type": "main",
"index": 0
}
]
]
},
"db_track_shipment": {
"main": [
[
{
"node": "fmt_track_shipment",
"type": "main",
"index": 0
}
]
]
},
"fmt_track_shipment": {
"main": [
[
{
"node": "resp_track_shipment",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
},
"active": false,
"versionId": "pawa-vapi-tools-v2",
"tags": []
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
postgres
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Pawa VAPI Tools v2 (live-schema). Uses postgres, httpRequest. Webhook trigger; 36 nodes.
Source: https://github.com/Nkubapawa2002/pawa1/blob/544fd4881cc415ca63e0075279f6fbc68ca68972/n8n/10_vapi_tools_v2.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Scraping. Uses httpRequest, postgres, @apify/n8n-nodes-apify, respondToWebhook. Webhook trigger; 61 nodes.
Workflow B — AI Listing Engine. Uses httpRequest, postgres, errorTrigger. Webhook trigger; 47 nodes.
LogSentinel Workflow. Uses postgres, emailSend, httpRequest. Webhook trigger; 44 nodes.
Fluxo de voluntárias ZendeskXANXBD. Uses functionItem, zendesk, httpRequest, postgres. Webhook trigger; 25 nodes.
Fluxo de voluntárias ZendeskXANXBD. Uses functionItem, zendesk, httpRequest, postgres. Webhook trigger; 25 nodes.