How to Send QR Codes via WhatsApp API: Event Ticketing Automation
Send personalized QR code tickets via WhatsApp using Rapiwa API. Generate QR codes in Python and Node.js, host them, and deliver via WhatsApp automatically after ticket purchase.
You can send QR code event tickets via WhatsApp by generating a QR code image, hosting it at a public URL, and sending it with Rapiwa's /send-image endpoint. When a customer buys a ticket, your system generates a unique QR code, uploads it to S3 or a CDN, and delivers it to the customer's WhatsApp within seconds. Rapiwa costs $5/month flat with no per-message fees.
What You'll Build
An automated ticketing system that:
- Generates a unique QR code for each ticket purchase
- Uploads the QR code to cloud storage
- Sends the QR code image to the customer via WhatsApp
- Includes ticket details in the caption (event name, date, seat, reference)
Result: Customers receive their QR code ticket on WhatsApp seconds after purchase — 98% open rate vs 20% for email.
Prerequisites
- Rapiwa account (free 3-day trial at rapiwa.com)
- Your Rapiwa API key (Dashboard → API Keys)
- Python 3.10+ or Node.js 18+
- AWS S3 or Cloudflare R2 for hosting QR images (or any CDN)
Step 1: Generate a QR Code in Python
pip install qrcode[pil] boto3 requests Pillow
import qrcode
import io
import boto3
import requests
import uuid
from PIL import Image, ImageDraw, ImageFont
def generate_ticket_qr(
ticket_id: str,
event_name: str,
customer_name: str,
event_date: str,
seat: str = None
) -> bytes:
"""
Generate a QR code image for an event ticket.
Returns the image as PNG bytes.
"""
# QR code data — what the scanner reads
qr_data = f"TICKET:{ticket_id}|EVENT:{event_name}|DATE:{event_date}"
if seat:
qr_data += f"|SEAT:{seat}"
# Generate QR code
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_H, # High error correction
box_size=10,
border=4
)
qr.add_data(qr_data)
qr.make(fit=True)
# Create QR code image with white background
qr_img = qr.make_image(fill_color="black", back_color="white")
qr_img = qr_img.convert('RGB')
# Add ticket information below the QR code
width, height = qr_img.size
new_height = height + 120 # Add space for text
final_img = Image.new('RGB', (width, new_height), 'white')
final_img.paste(qr_img, (0, 0))
# Add text (optional — requires a font file)
draw = ImageDraw.Draw(final_img)
draw.text((10, height + 10), f"Event: {event_name}", fill='black')
draw.text((10, height + 35), f"Date: {event_date}", fill='black')
draw.text((10, height + 60), f"Ref: {ticket_id}", fill='gray')
if seat:
draw.text((10, height + 85), f"Seat: {seat}", fill='black')
# Convert to bytes
buffer = io.BytesIO()
final_img.save(buffer, format='PNG', optimize=True)
return buffer.getvalue()
def upload_to_s3(image_bytes: bytes, ticket_id: str) -> str:
"""
Upload QR code image to S3 and return a public URL.
Bucket must have public read access configured.
"""
s3 = boto3.client('s3',
aws_access_key_id='YOUR_AWS_KEY',
aws_secret_access_key='YOUR_AWS_SECRET',
region_name='us-east-1'
)
bucket_name = 'your-ticket-bucket'
key = f'tickets/{ticket_id}.png'
s3.put_object(
Bucket=bucket_name,
Key=key,
Body=image_bytes,
ContentType='image/png',
ACL='public-read'
)
return f'https://{bucket_name}.s3.amazonaws.com/{key}'
def send_ticket_via_whatsapp(
phone: str,
customer_name: str,
event_name: str,
event_date: str,
event_location: str,
ticket_id: str,
image_url: str,
api_key: str
) -> dict:
"""
Send the QR code ticket via WhatsApp using Rapiwa API.
"""
caption = (
f"🎫 Your Ticket is Confirmed!\n\n"
f"Hi {customer_name}!\n\n"
f"📅 *{event_name}*\n"
f"🗓️ Date: {event_date}\n"
f"📍 Location: {event_location}\n"
f"🎟️ Ticket Ref: {ticket_id}\n\n"
f"Show this QR code at the entrance.\n"
f"See you there! 🎉"
)
response = requests.post(
'https://app.rapiwa.com/send-image',
headers={'Authorization': f'Bearer {api_key}'},
json={
'number': phone,
'imageUrl': image_url,
'caption': caption
},
timeout=30
)
return response.json()
def process_ticket_purchase(purchase: dict, api_key: str) -> dict:
"""
Full pipeline: generate QR → upload → send WhatsApp ticket.
Call this when a ticket purchase is confirmed.
"""
ticket_id = purchase.get('ticket_id') or str(uuid.uuid4())[:8].upper()
# 1. Generate QR code image
qr_bytes = generate_ticket_qr(
ticket_id=ticket_id,
event_name=purchase['event_name'],
customer_name=purchase['customer_name'],
event_date=purchase['event_date'],
seat=purchase.get('seat')
)
# 2. Upload to S3
image_url = upload_to_s3(qr_bytes, ticket_id)
# 3. Send via WhatsApp
result = send_ticket_via_whatsapp(
phone=purchase['phone'],
customer_name=purchase['customer_name'],
event_name=purchase['event_name'],
event_date=purchase['event_date'],
event_location=purchase['event_location'],
ticket_id=ticket_id,
image_url=image_url,
api_key=api_key
)
return {
'ticket_id': ticket_id,
'image_url': image_url,
'whatsapp_result': result
}
# Example usage
if __name__ == '__main__':
purchase = {
'ticket_id': 'TKT-2026-001',
'customer_name': 'Sarah Johnson',
'phone': '8801234567890',
'event_name': 'TechConf 2026',
'event_date': 'July 15, 2026 at 9:00 AM',
'event_location': 'Dhaka Convention Centre',
'seat': 'A-42'
}
result = process_ticket_purchase(purchase, api_key='YOUR_API_KEY')
print(f"Ticket sent! ID: {result['ticket_id']}")
print(f"WhatsApp status: {result['whatsapp_result']['status']}")
Step 2: Node.js Implementation
npm install qrcode axios @aws-sdk/client-s3
// ticketing.js
const QRCode = require('qrcode');
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
const fetch = require('node-fetch');
const s3 = new S3Client({ region: 'us-east-1' });
const RAPIWA_API_KEY = 'YOUR_API_KEY';
const BUCKET_NAME = 'your-ticket-bucket';
async function generateQRCodeBuffer(ticketData) {
return QRCode.toBuffer(ticketData, {
errorCorrectionLevel: 'H',
type: 'png',
width: 400,
margin: 2
});
}
async function uploadToS3(buffer, ticketId) {
const key = `tickets/${ticketId}.png`;
await s3.send(new PutObjectCommand({
Bucket: BUCKET_NAME,
Key: key,
Body: buffer,
ContentType: 'image/png',
ACL: 'public-read'
}));
return `https://${BUCKET_NAME}.s3.amazonaws.com/${key}`;
}
async function sendTicketWhatsApp(ticket) {
const { phone, customerName, eventName, eventDate, location, ticketId, imageUrl } = ticket;
const caption = `🎫 Ticket Confirmed!\n\n` +
`Hi ${customerName}!\n\n` +
`📅 *${eventName}*\n` +
`🗓️ ${eventDate}\n` +
`📍 ${location}\n` +
`🎟️ Ref: ${ticketId}\n\n` +
`Show this QR code at the entrance. See you there! 🎉`;
const response = await fetch('https://app.rapiwa.com/send-image', {
method: 'POST',
headers: {
'Authorization': `Bearer ${RAPIWA_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ number: phone, imageUrl, caption })
});
return response.json();
}
async function processTicketPurchase(purchase) {
const ticketId = purchase.ticketId || Math.random().toString(36).slice(2, 10).toUpperCase();
const qrData = `TICKET:${ticketId}|EVENT:${purchase.eventName}|DATE:${purchase.eventDate}`;
const qrBuffer = await generateQRCodeBuffer(qrData);
const imageUrl = await uploadToS3(qrBuffer, ticketId);
const result = await sendTicketWhatsApp({ ...purchase, ticketId, imageUrl });
return { ticketId, imageUrl, whatsappResult: result };
}
// Usage
processTicketPurchase({
ticketId: 'TKT-001',
phone: '8801234567890',
customerName: 'Sarah Johnson',
eventName: 'TechConf 2026',
eventDate: 'July 15, 2026',
location: 'Dhaka Convention Centre'
}).then(result => console.log('Ticket sent:', result));
Test cURL to verify your API key:
curl -X POST https://app.rapiwa.com/send-image \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"number": "8801234567890",
"imageUrl": "https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=TEST-TICKET-001",
"caption": "🎫 Test Ticket — TechConf 2026\nRef: TEST-TICKET-001\nShow this QR at the entrance!"
}'
Ticket Scanning at the Event
To validate QR codes at the entrance, build a scanner app:
# scanner.py — run on a tablet/phone at the entrance
import cv2
import requests
def scan_and_validate(ticket_api_url: str, api_key: str):
"""Camera-based QR code scanner and validator."""
cap = cv2.VideoCapture(0)
detector = cv2.QRCodeDetector()
scanned = set() # Prevent re-scans
while True:
_, img = cap.read()
data, _, _ = detector.detectAndDecode(img)
if data and data not in scanned:
# Validate against your API
response = requests.post(
f"{ticket_api_url}/validate",
headers={"Authorization": f"Bearer {api_key}"},
json={"qr_data": data}
)
result = response.json()
if result['valid']:
print(f"✅ VALID — {result['customer_name']}: {data}")
scanned.add(data)
else:
print(f"❌ INVALID or ALREADY USED: {data}")
cv2.imshow('Ticket Scanner', img)
if cv2.waitKey(1) == ord('q'):
break
cap.release()
Common Errors and Fixes
- Image not loading on WhatsApp: The S3 bucket must be publicly accessible. Check bucket policy allows
s3:GetObjectfor anonymous users - QR code not scanning: Ensure error correction level is
H(High) and the image is at least 300x300 pixels - Large QR code file size: Use PNG format with
optimize=True. QR codes compress very well — should be under 30KB - 401 from Rapiwa: API key expired — regenerate in Dashboard → API Keys
FAQ
Can I send QR codes without S3?
Yes. Use any public CDN or file hosting. You can also use https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=TICKET_ID as a free QR generation URL (no upload needed for simple QR codes).
Can I use Rapiwa to send QR codes for purposes other than tickets?
Yes — QR codes for product authentication, loyalty program check-ins, appointment confirmations, access control, and more. The pattern is the same: generate QR → host at public URL → send via /send-image.
Does Rapiwa charge per QR code ticket sent? No. Rapiwa charges $5/month flat with no per-message fees. Send 10 or 10,000 ticket QR codes at the same monthly price.
How do I prevent duplicate QR code use (one ticket used multiple times)?
After the first successful scan, mark the ticket as used=true in your database. The scanner checks this flag before granting entry.
Can attendees add the ticket to Apple Wallet or Google Wallet instead? Yes, but that's separate from WhatsApp delivery. Send the WhatsApp QR image first (immediate) and optionally include a link to download the Wallet pass in the caption.
