using AccessGrid;
using System;
using System.Threading.Tasks;
public async Task ListCardsAsync()
{
var accountId = Environment.GetEnvironmentVariable("ACCOUNT_ID");
var secretKey = Environment.GetEnvironmentVariable("SECRET_KEY");
var client = new AccessGridClient(accountId, secretKey);
// Get filtered keys by template
var templateKeys = await client.AccessCards.ListAsync(new ListKeysRequest
{
TemplateId = "0xd3adb00b5"
});
// Get filtered keys by state
var activeKeys = await client.AccessCards.ListAsync(new ListKeysRequest
{
State = "active"
});
// Print keys
foreach (var key in allKeys)
{
Console.WriteLine($"Key ID: {key.Id}, Name: {key.FullName}, State: {key.State}");
}
}
public class ListKeysRequest
{
public string TemplateId { get; set; }
public string State { get; set; }
}
import com.organization.accessgrid.AccessGridClient;
import com.organization.accessgrid.model.Card;
import com.organization.accessgrid.model.ListKeysParams;
import java.util.List;
public class AccessCardService {
private final AccessGridClient client;
public AccessCardService() {
String accountId = System.getenv("ACCOUNT_ID");
String secretKey = System.getenv("SECRET_KEY");
this.client = new AccessGridClient(accountId, secretKey);
}
public void listCards() throws AccessGridException {
// Get filtered keys by template
ListKeysParams templateFilter = ListKeysParams.builder()
.templateId("0xd3adb00b5")
.build();
List<Card> templateKeys = client.accessCards().list(templateFilter);
// Get filtered keys by state
ListKeysParams stateFilter = ListKeysParams.builder()
.state("active")
.build();
List<Card> activeKeys = client.accessCards().list(stateFilter);
// Print keys
for (Card key : allKeys) {
System.out.printf("Key ID: %s, Name: %s, State: %s%n",
key.getId(),
key.getFullName(),
key.getState());
}
}
}
Issue Access Pass
Enable a pass to be added to be created and installed on their mobile device with the constraints set in the AccessGrid console and this API call. Returns a landing page for installing the access pass.
card_template_id
string
All
Unique identifier for the card template to use
employee_id
string
Corporate
Unique identifier for the employee
tag_id
nullable string
All
Tag identifier associated with the Access Pass, only allowed if your card template has key diversification enabled. Must be 7 bytes, 14 chars, ex: DF517FC87144DF
site_code
nullable string
All
Site code from H10301 (26 bit) format. Must be number under 255. Required if `file_data` not present.
card_number
string
All
Site code from H10301 (26 bit) format. Must be number under 65,535
file_data
nullable string
All
Up to 64 characters of hexadecimal data (0-9, A-F). Only used when implementing proprietary data formats.
full_name
string
All
Full name of the Access Pass owner
email
nullable string
All
Email address of the Access Pass owner - if you have a SendGrid or Postmark integration, then we will send a text message on your behalf
phone_number
nullable string
All
Contact phone number for the Access Pass owner - if you have a Twilio integration, then we will send a text message on your behalf
using AccessGrid;
using Microsoft.Extensions.Configuration;
// Assuming you're in a class
public async Task ProvisionCardAsync()
{
var accountId = Environment.GetEnvironmentVariable("ACCOUNT_ID");
var secretKey = Environment.GetEnvironmentVariable("SECRET_KEY");
var client = new AccessGridClient(accountId, secretKey);
var card = await client.AccessCards.ProvisionAsync(new ProvisionCardRequest
{
CardTemplateId = "0xd3adb00b5",
EmployeeId = "123456789",
TagId = "DDEADB33FB00B5",
AllowOnMultipleDevices = true,
FullName = "Employee name",
Email = "[email protected]",
PhoneNumber = "+19547212241",
Classification = "full_time",
StartDate = DateTime.UtcNow,
ExpirationDate = DateTime.Parse("2025-02-22T21:04:03.664Z").ToUniversalTime(),
EmployeePhoto = "[image_in_base64_encoded_format]",
Title = "Engineering Manager",
Metadata = new Dictionary<string, object>
{
["department"] = "engineering",
["badge_type"] = "contractor"
}
});
Console.WriteLine($"Install URL: {card.Url}");
}
// The request model would likely be defined like this
public class ProvisionCardRequest
{
public string CardTemplateId { get; set; } = string.Empty;
public string EmployeeId { get; set; } = string.Empty;
public string TagId { get; set; } = string.Empty;
public bool AllowOnMultipleDevices { get; set; }
public string FullName { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public string PhoneNumber { get; set; } = string.Empty;
public string Classification { get; set; } = string.Empty;
public DateTime StartDate { get; set; }
public DateTime ExpirationDate { get; set; }
public string EmployeePhoto { get; set; } = string.Empty;
public string Title { get; set; } = string.Empty;
}
// And the response might look like this
public class ProvisionCardResponse
{
public string Url { get; set; } = string.Empty;
// Other properties that might come back from the API...
}
import com.organization.accessgrid.AccessGridClient;
import com.organization.accessgrid.model.ProvisionCardRequest;
import com.organization.accessgrid.model.Card;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
public class AccessCardService {
private final AccessGridClient client;
public AccessCardService() {
String accountId = System.getenv("ACCOUNT_ID");
String secretKey = System.getenv("SECRET_KEY");
this.client = new AccessGridClient(accountId, secretKey);
}
public Card provisionCard() throws AccessGridException {
ProvisionCardRequest request = ProvisionCardRequest.builder()
.cardTemplateId("0xd3adb00b5")
.employeeId("123456789")
.tagId("DDEADB33FB00B5")
.allowOnMultipleDevices(true)
.fullName("Employee name")
.email("[email protected]")
.phoneNumber("+19547212241")
.classification("full_time")
.startDate(ZonedDateTime.now().format(DateTimeFormatter.ISO_INSTANT))
.expirationDate("2025-02-22T21:04:03.664Z")
.employeePhoto("[image_in_base64_encoded_format]")
.title("Engineering Manager")
.metadata(Map.of(
"department", "engineering",
"badge_type", "contractor"
))
.build();
Card card = client.accessCards().provision(request);
System.out.printf("Install URL: %s%n", card.getUrl());
return card;
}
}
// The request model would likely be defined like this
@Getter
@Builder
public class ProvisionCardRequest {
private final String cardTemplateId;
private final String employeeId;
private final String tagId;
private final boolean allowOnMultipleDevices;
private final String fullName;
private final String email;
private final String phoneNumber;
private final String classification;
private final String startDate;
private final String expirationDate;
private final String employeePhoto;
private final String title;
}
// And the response model
@Getter
public class Card {
private final String url;
// Other properties...
}
// Usage example
public class Example {
public static void main(String[] args) {
try {
AccessCardService service = new AccessCardService();
Card card = service.provisionCard();
} catch (AccessGridException e) {
System.err.println("Error provisioning card: " + e.getMessage());
}
}
}
using AccessGrid;
public async Task SuspendCardAsync()
{
var accountId = Environment.GetEnvironmentVariable("ACCOUNT_ID");
var secretKey = Environment.GetEnvironmentVariable("SECRET_KEY");
var client = new AccessGridClient(accountId, secretKey);
await client.AccessCards.SuspendAsync("0xc4rd1d");
Console.WriteLine("Card suspended successfully");
}
import com.organization.accessgrid.AccessGridClient;
public class AccessCardService {
private final AccessGridClient client;
public AccessCardService() {
String accountId = System.getenv("ACCOUNT_ID");
String secretKey = System.getenv("SECRET_KEY");
this.client = new AccessGridClient(accountId, secretKey);
}
public void suspendCard() throws AccessGridException {
client.accessCards().suspend("0xc4rd1d");
System.out.println("Card suspended successfully");
}
}
using AccessGrid;
public async Task ResumeCardAsync()
{
var accountId = Environment.GetEnvironmentVariable("ACCOUNT_ID");
var secretKey = Environment.GetEnvironmentVariable("SECRET_KEY");
var client = new AccessGridClient(accountId, secretKey);
await client.AccessCards.ResumeAsync("0xc4rd1d");
Console.WriteLine("Card resumed successfully");
}
import com.organization.accessgrid.AccessGridClient;
public class AccessCardService {
private final AccessGridClient client;
public AccessCardService() {
String accountId = System.getenv("ACCOUNT_ID");
String secretKey = System.getenv("SECRET_KEY");
this.client = new AccessGridClient(accountId, secretKey);
}
public void resumeCard() throws AccessGridException {
client.accessCards().resume("0xc4rd1d");
System.out.println("Card resumed successfully");
}
}
If you'd like to force the removal of an Access Pass from it's current holder - unlinking does not make a key uninstallable, it simply disables it on the users phone.
card_id
string
All
Unique identifier of the Access Pass to unlink from its current holder, sent as part of the URL
using AccessGrid;
public async Task UnlinkCardAsync()
{
var accountId = Environment.GetEnvironmentVariable("ACCOUNT_ID");
var secretKey = Environment.GetEnvironmentVariable("SECRET_KEY");
var client = new AccessGridClient(accountId, secretKey);
await client.AccessCards.UnlinkAsync("0xc4rd1d");
Console.WriteLine("Card unlinked successfully");
}
import com.organization.accessgrid.AccessGridClient;
public class AccessCardService {
private final AccessGridClient client;
public AccessCardService() {
String accountId = System.getenv("ACCOUNT_ID");
String secretKey = System.getenv("SECRET_KEY");
this.client = new AccessGridClient(accountId, secretKey);
}
public void unlinkCard() throws AccessGridException {
client.accessCards().unlink("0xc4rd1d");
System.out.println("Card unlinked successfully");
}
}
using AccessGrid;
public async Task DeleteCardAsync()
{
var accountId = Environment.GetEnvironmentVariable("ACCOUNT_ID");
var secretKey = Environment.GetEnvironmentVariable("SECRET_KEY");
var client = new AccessGridClient(accountId, secretKey);
await client.AccessCards.DeleteAsync("0xc4rd1d");
Console.WriteLine("Card deleted successfully");
}
import com.organization.accessgrid.AccessGridClient;
public class AccessCardService {
private final AccessGridClient client;
public AccessCardService() {
String accountId = System.getenv("ACCOUNT_ID");
String secretKey = System.getenv("SECRET_KEY");
this.client = new AccessGridClient(accountId, secretKey);
}
public void deleteCard() throws AccessGridException {
client.accessCards().delete("0xc4rd1d");
System.out.println("Card deleted successfully");
}
}
Only available for enterprise customers - allows you to create an empty card template using our console API.
name
string
All
The name to display for this card template in the AccessGrid console UI
platform
string
All
Must be one of `apple` or `google`
use_case
string
All
Must be `employee_badge` or `hotel`
protocol
string
Apple
Must be `desfire` or `seos` - HID Seos only available for enterprise customers
allow_on_multiple_devices
nullable boolean
All
False by default. Set to true if you'd like to enable the Access Passes issued using this template to exist on multiple devices (think phone and watch)
watch_count
nullable integer
Apple
Only allowed to be set if `allow_on_multiple_devices` is set to true. Any number between 1-5 is acceptable.
iphone_count
nullable integer
Apple
Only allowed to be set if `allow_on_multiple_devices` is set to true. Any number between 1-5 is acceptable.
design
nullable object
All
Object representing card template design
background_color
nullable string
Apple
Must be a 6 character hexadecimal value for the background color of the template, in the event that there is no background_image provided, i.e. #FFFFFF
label_color
nullable string
Apple
Must be a 6 character hexadecimal value for the label color for the template, i.e. #000000
label_secondary_color
nullable string
Apple
Must be a 6 character hexadecimal value for the secondary label color for the template, i.e. #333333
background_image
nullable string
All
Base64 encoded image of the card templates background
logo_image
nullable string
All
Base64 encoded image of the card templates logo (located in the top left)
icon_image
nullable string
Apple
Base64 encoded image of the card templates icon (used in sharing and notifications)
support_info
nullable object
Apple
Information for users that shows up on the back of the Access Pass
support_url
nullable string
Apple
Shows on the back of the issued Access Pass.
support_phone_number
nullable string
Apple
Shows on the back of the issued Access Pass.
support_email
nullable string
Apple
Shows on the back of the issued Access Pass.
privacy_policy_url
nullable string
Apple
Shows on the back of the issued Access Pass.
terms_and_conditions_url
nullable string
Apple
Shows on the back of the issued Access Pass.
metadata
nullable object
All
Custom JSON object for storing additional metadata associated with the pass template. Can contain any valid JSON data.
Only available for enterprise customers - allows you to update certain attributes of an existing card template using our console API.
card_template_id
string
All
The card template id you want to update
name
string
All
The name to display for this card template in the AccessGrid console UI
allow_on_multiple_devices
nullable boolean
All
False by default. Set to true if you'd like to enable the Access Passes issued using this template to exist on multiple devices (think phone and watch)
watch_count
nullable integer
Apple
Only allowed to be set if `allow_on_multiple_devices` is set to true. Any number between 1-5 is acceptable.
iphone_count
nullable integer
Apple
Only allowed to be set if `allow_on_multiple_devices` is set to true. Any number between 1-5 is acceptable.
support_info
nullable object
Apple
Information for users that shows up on the back of the Access Pass
support_url
nullable string
Apple
Shows on the back of the issued Access Pass.
support_phone_number
nullable string
Apple
Shows on the back of the issued Access Pass.
support_email
nullable string
Apple
Shows on the back of the issued Access Pass.
privacy_policy_url
nullable string
Apple
Shows on the back of the issued Access Pass.
terms_and_conditions_url
nullable string
Apple
Shows on the back of the issued Access Pass.
metadata
nullable object
All
Custom JSON object for storing additional metadata associated with the pass template. Can contain any valid JSON data.
Only available for enterprise customers - allows you to read full event log for a given card template, including which passes were issued, how and by whom using our console API.
card_template_id
nullable string
All
Unique identifier for the card template to look up
filters
nullable object
All
Filters to reduce result size of event logs
device
nullable string
All
Must be either `mobile` or `watch`
start_date
nullable datetime
All
Must be in ISO8601 format
end_date
nullable datetime
All
Must be in ISO8601 format
event_type
nullable string
All
Must be either `issue`, `install`, `update`, `suspend`, `resume`, or `unlink`
using AccessGrid;
using System;
public async Task GetEventLogAsync()
{
var accountId = Environment.GetEnvironmentVariable("ACCOUNT_ID");
var secretKey = Environment.GetEnvironmentVariable("SECRET_KEY");
var client = new AccessGridClient(accountId, secretKey);
var events = await client.Console.EventLogAsync(
"0xd3adb00b5",
new EventLogFilters
{
Device = "mobile",
StartDate = DateTime.UtcNow.AddDays(-30),
EndDate = DateTime.UtcNow,
EventType = "install"
});
foreach (var evt in events)
{
Console.WriteLine($"Event: {evt.Type} at {evt.Timestamp} by {evt.UserId}");
}
}
import com.organization.accessgrid.AccessGridClient;
import com.organization.accessgrid.model.Event;
import com.organization.accessgrid.model.EventLogFilters;
import java.time.ZonedDateTime;
import java.util.List;
public class ConsoleService {
private final AccessGridClient client;
public ConsoleService() {
String accountId = System.getenv("ACCOUNT_ID");
String secretKey = System.getenv("SECRET_KEY");
this.client = new AccessGridClient(accountId, secretKey);
}
public void getEventLog() throws AccessGridException {
EventLogFilters filters = EventLogFilters.builder()
.device("mobile")
.startDate(ZonedDateTime.now().minusDays(30))
.endDate(ZonedDateTime.now())
.eventType("install")
.build();
List<Event> events = client.console().eventLog("0xd3adb00b5", filters);
for (Event event : events) {
System.out.printf("Event: %s at %s by %s%n",
event.getType(),
event.getTimestamp(),
event.getUserId());
}
}
}
<?php
require 'vendor/autoload.php';
use AccessGridClient;
$accountId = $_ENV['ACCOUNT_ID'];
$secretKey = $_ENV['SECRET_KEY'];
$client = new Client($accountId, $secretKey);
$events = $client->console->eventLog([
'card_template_id' => '0xd3adb00b5',
'filters' => [
'device' => 'mobile',
'start_date' => (new DateTime('30 days ago'))->format('c'),
'end_date' => (new DateTime('now'))->format('c'),
'event_type' => 'install'
]
]);
foreach ($events as $event) {
echo "Event: {$event->type} at {$event->timestamp} by {$event->user_id}\n";
}
Only available for enterprise customers - provides the required identifiers for iOS mobile provisioning for an access pass associated with a given card template.
card_template_id
string
All
The card template ID (ex_id) the access pass belongs to
access_pass_ex_id
string
All
The ex_id of the access pass to retrieve provisioning identifiers for
We use simple bearer tokens for authentication, which is generated whenever you create a new webhook. In general if your server responds with either 200, or 201, then we will not send the event occurrence again.
If we cannot reach your server, or you respond with non 200/201 response code, we will try again for up to 6 hours before dropping the delivery attempts.
require 'sinatra'
require 'json'
# Webhook endpoint to receive AccessGrid events
post '/webhooks' do
request.body.rewind
payload = JSON.parse(request.body.read)
# Verify it's a CloudEvents payload
unless payload['specversion'] == '1.0'
halt 400, { error: 'Invalid CloudEvents format' }.to_json
end
# Handle different event types
case payload['type']
when 'ag.access_pass.issued'
handle_access_pass_issued(payload['data'])
when 'ag.access_pass.activated'
handle_access_pass_activated(payload['data'])
when 'ag.card_template.published'
handle_template_published(payload['data'])
else
puts "Unknown event type: #{payload['type']}"
end
# Always return 200 to acknowledge receipt
status 200
{ received: true }.to_json
end
def handle_access_pass_issued(data)
puts "Access pass issued: #{data['access_pass_id']}"
# Your custom logic here
end
def handle_access_pass_activated(data)
puts "Access pass activated: #{data['access_pass_id']}"
puts "Device: #{data['device']['type']}"
# Your custom logic here
end
def handle_template_published(data)
puts "Template published: #{data['card_template_id']}"
# Your custom logic here
end
const express = require('express');
const app = express();
app.use(express.json({
type: ['application/json', 'application/cloudevents+json']
}));
// Webhook endpoint to receive AccessGrid events
app.post('/webhooks', (req, res) => {
const payload = req.body;
// Verify it's a CloudEvents payload
if (payload.specversion !== '1.0') {
return res.status(400).json({ error: 'Invalid CloudEvents format' });
}
// Handle different event types
switch (payload.type) {
case 'ag.access_pass.issued':
handleAccessPassIssued(payload.data);
break;
case 'ag.access_pass.activated':
handleAccessPassActivated(payload.data);
break;
case 'ag.card_template.published':
handleTemplatePublished(payload.data);
break;
default:
console.log(`Unknown event type: ${payload.type}`);
}
// Always return 200 to acknowledge receipt
res.status(200).json({ received: true });
});
function handleAccessPassIssued(data) {
console.log(`Access pass issued: ${data.access_pass_id}`);
// Your custom logic here
}
function handleAccessPassActivated(data) {
console.log(`Access pass activated: ${data.access_pass_id}`);
console.log(`Device: ${data.device.type}`);
// Your custom logic here
}
function handleTemplatePublished(data) {
console.log(`Template published: ${data.card_template_id}`);
// Your custom logic here
}
app.listen(3000, () => {
console.log('Webhook server listening on port 3000');
});
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/webhooks', methods=['POST'])
def webhook():
payload = request.get_json()
# Verify it's a CloudEvents payload
if payload.get('specversion') != '1.0':
return jsonify({'error': 'Invalid CloudEvents format'}), 400
# Handle different event types
event_type = payload.get('type')
data = payload.get('data', {})
if event_type == 'ag.access_pass.issued':
handle_access_pass_issued(data)
elif event_type == 'ag.access_pass.activated':
handle_access_pass_activated(data)
elif event_type == 'ag.card_template.published':
handle_template_published(data)
else:
print(f"Unknown event type: {event_type}")
# Always return 200 to acknowledge receipt
return jsonify({'received': True}), 200
def handle_access_pass_issued(data):
print(f"Access pass issued: {data['access_pass_id']}")
# Your custom logic here
def handle_access_pass_activated(data):
print(f"Access pass activated: {data['access_pass_id']}")
print(f"Device: {data['device']['type']}")
# Your custom logic here
def handle_template_published(data):
print(f"Template published: {data['card_template_id']}")
# Your custom logic here
if __name__ == '__main__':
app.run(port=3000)
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
)
type CloudEvent struct {
SpecVersion string `json:\"specversion\"`
ID string `json:\"id\"`
Source string `json:\"source\"`
Type string `json:\"type\"`
Time string `json:\"time\"`
Data map[string]interface{} `json:\"data\"`
}
func webhookHandler(w http.ResponseWriter, r *http.Request) {
var event CloudEvent
if err := json.NewDecoder(r.Body).Decode(&event); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
// Verify it's a CloudEvents payload
if event.SpecVersion != "1.0" {
http.Error(w, "Invalid CloudEvents format", http.StatusBadRequest)
return
}
// Handle different event types
switch event.Type {
case "ag.access_pass.issued":
handleAccessPassIssued(event.Data)
case "ag.access_pass.activated":
handleAccessPassActivated(event.Data)
case "ag.card_template.published":
handleTemplatePublished(event.Data)
default:
log.Printf("Unknown event type: %s", event.Type)
}
// Always return 200 to acknowledge receipt
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]bool{"received": true})
}
func handleAccessPassIssued(data map[string]interface{}) {
fmt.Printf("Access pass issued: %v\\n", data["access_pass_id"])
// Your custom logic here
}
func handleAccessPassActivated(data map[string]interface{}) {
fmt.Printf("Access pass activated: %v\\n", data["access_pass_id"])
if device, ok := data["device"].(map[string]interface{}); ok {
fmt.Printf("Device: %v\\n", device["type"])
}
// Your custom logic here
}
func handleTemplatePublished(data map[string]interface{}) {
fmt.Printf("Template published: %v\\n", data["card_template_id"])
// Your custom logic here
}
func main() {
http.HandleFunc("/webhooks", webhookHandler)
log.Println("Webhook server listening on port 3000")
log.Fatal(http.ListenAndServe(":3000", nil))
}
using System;
using System.IO;
using System.Text.Json;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost(\"/webhooks\", async (HttpContext context) =>
{
using var reader = new StreamReader(context.Request.Body);
var body = await reader.ReadToEndAsync();
var payload = JsonSerializer.Deserialize<CloudEvent>(body);
// Verify it's a CloudEvents payload
if (payload?.SpecVersion != \"1.0\")
{
context.Response.StatusCode = 400;
await context.Response.WriteAsJsonAsync(new { error = \"Invalid CloudEvents format\" });
return;
}
// Handle different event types
switch (payload.Type)
{
case \"ag.access_pass.issued\":
HandleAccessPassIssued(payload.Data);
break;
case \"ag.access_pass.activated\":
HandleAccessPassActivated(payload.Data);
break;
case \"ag.card_template.published\":
HandleTemplatePublished(payload.Data);
break;
default:
Console.WriteLine($\"Unknown event type: {payload.Type}\");
break;
}
// Always return 200 to acknowledge receipt
await context.Response.WriteAsJsonAsync(new { received = true });
});
void HandleAccessPassIssued(JsonElement data)
{
Console.WriteLine($\"Access pass issued: {data.GetProperty(\\\"access_pass_id\\\").GetString()}\");
// Your custom logic here
}
void HandleAccessPassActivated(JsonElement data)
{
Console.WriteLine($\"Access pass activated: {data.GetProperty(\\\"access_pass_id\\\").GetString()}\");
if (data.TryGetProperty(\"device\", out var device))
{
Console.WriteLine($\"Device: {device.GetProperty(\\\"type\\\").GetString()}\");
}
// Your custom logic here
}
void HandleTemplatePublished(JsonElement data)
{
Console.WriteLine($\"Template published: {data.GetProperty(\\\"card_template_id\\\").GetString()}\");
// Your custom logic here
}
app.Run(\"http://localhost:3000\");
record CloudEvent(string SpecVersion, string Id, string Source, string Type, string Time, JsonElement Data);
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpExchange;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import java.io.*;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
public class WebhookServer {
private static final Gson gson = new Gson();
public static void main(String[] args) throws IOException {
HttpServer server = HttpServer.create(new InetSocketAddress(3000), 0);
server.createContext(\"/webhooks\", new WebhookHandler());
server.start();
System.out.println(\"Webhook server listening on port 3000\");
}
static class WebhookHandler implements HttpHandler {
@Override
public void handle(HttpExchange exchange) throws IOException {
if (!\"POST\".equals(exchange.getRequestMethod())) {
exchange.sendResponseHeaders(405, 0);
exchange.close();
return;
}
InputStream is = exchange.getRequestBody();
String body = new String(is.readAllBytes(), StandardCharsets.UTF_8);
JsonObject payload = gson.fromJson(body, JsonObject.class);
// Verify it's a CloudEvents payload
if (!\"1.0\".equals(payload.get(\"specversion\").getAsString())) {
String response = \"{\\\"error\\\": \\\"Invalid CloudEvents format\\\"}\";
exchange.sendResponseHeaders(400, response.length());
OutputStream os = exchange.getResponseBody();
os.write(response.getBytes());
os.close();
return;
}
// Handle different event types
String type = payload.get(\"type\").getAsString();
JsonObject data = payload.getAsJsonObject(\"data\");
switch (type) {
case \"ag.access_pass.issued\":
handleAccessPassIssued(data);
break;
case \"ag.access_pass.activated\":
handleAccessPassActivated(data);
break;
case \"ag.card_template.published\":
handleTemplatePublished(data);
break;
default:
System.out.println(\"Unknown event type: \" + type);
}
// Always return 200 to acknowledge receipt
String response = \"{\\\"received\\\": true}\";
exchange.sendResponseHeaders(200, response.length());
OutputStream os = exchange.getResponseBody();
os.write(response.getBytes());
os.close();
}
private void handleAccessPassIssued(JsonObject data) {
System.out.println(\"Access pass issued: \" + data.get(\"access_pass_id\").getAsString());
// Your custom logic here
}
private void handleAccessPassActivated(JsonObject data) {
System.out.println(\"Access pass activated: \" + data.get(\"access_pass_id\").getAsString());
if (data.has(\"device\")) {
JsonObject device = data.getAsJsonObject(\"device\");
System.out.println(\"Device: \" + device.get(\"type\").getAsString());
}
// Your custom logic here
}
private void handleTemplatePublished(JsonObject data) {
System.out.println(\"Template published: \" + data.get(\"card_template_id\").getAsString());
// Your custom logic here
}
}
}
<?php
require 'vendor/autoload.php';
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;
$app = AppFactory::create();
$app->addBodyParsingMiddleware();
$app->post('/webhooks', function (Request $request, Response $response) {
$payload = $request->getParsedBody();
// Verify it's a CloudEvents payload
if (!isset($payload['specversion']) || $payload['specversion'] !== '1.0') {
$response->getBody()->write(json_encode(['error' => 'Invalid CloudEvents format']));
return $response->withStatus(400)->withHeader('Content-Type', 'application/json');
}
// Handle different event types
$type = $payload['type'] ?? '';
$data = $payload['data'] ?? [];
switch ($type) {
case 'ag.access_pass.issued':
handleAccessPassIssued($data);
break;
case 'ag.access_pass.activated':
handleAccessPassActivated($data);
break;
case 'ag.card_template.published':
handleTemplatePublished($data);
break;
default:
error_log(\"Unknown event type: $type\");
}
// Always return 200 to acknowledge receipt
$response->getBody()->write(json_encode(['received' => true]));
return $response->withHeader('Content-Type', 'application/json');
});
function handleAccessPassIssued($data) {
error_log(\"Access pass issued: {$data['access_pass_id']}\");
// Your custom logic here
}
function handleAccessPassActivated($data) {
error_log(\"Access pass activated: {$data['access_pass_id']}\");
if (isset($data['device'])) {
error_log(\"Device: {$data['device']['type']}\");
}
// Your custom logic here
}
function handleTemplatePublished($data) {
error_log(\"Template published: {$data['card_template_id']}\");
// Your custom logic here
}
$app->run();