Sync Customers from Your App
When you embed Prismatic into your app, you need to authenticate your customer users as users of a specific customer in Prismatic. That's done by specifying a customer's External ID when you generate a signed JWT for your customer user. Those customers need to exist in Prismatic prior to generating a signed JWT (though you can programmatically check for the existance of a customer and create one when you generate the JWT in your backend).
In this tutorial, we'll cover how to sync customers from your app to Prismatic using Prismatic's GraphQL API. We'll highlight External IDs, which are a way to assign a unique external identifier to a customer.
Setting up
We'll use Python in this tutorial, though the same ideas can be adapted to any language that has a GraphQL client library. Full code shown in this tutorial can be found on GitHub.
To start, we'll add gql
to our dependencies for our Python project:
pip install gql
Next, we'll import our necessary libraries and create a GraphQL client so we can run queries against Prismatic's API. We'll define the transport layer for getting to our API (HTTP), which will include an authorization header to authenticate our client against Prismatic:
import json
import os
import sys
from gql import gql, Client
from gql.transport.requests import RequestsHTTPTransport
token = os.environ['PRISMATIC_API_KEY']
api_endpoint = "https://app.prismatic.io/api/"
transport = RequestsHTTPTransport(
url=api_endpoint,
headers={'Authorization': f'Bearer {token}'}
)
client = Client(transport=transport)
This code assumes that an environment variable, PRISMATIC_API_KEY
, has been set (it's best practice not to hard-code API keys).
To get an API key into our environment variables, we can leverage the me:token
prism subcommand:
export PRISMATIC_API_KEY=$(prism me:token)
Now that we have a working client, let's write some GraphQL queries and mutations to manage customers within Prismatic:
GraphQL queries and mutations
To sync customers, we'll use the customers query and createCustomer and deleteCustomer mutations.
In GraphQL lingo, a query is similar to a SELECT
statement in SQL.
You query for a particular set of information in a read-only way.
A mutation is similar to an INSERT
, UPDATE
, or DELETE
statement in SQL.
You mutate data by creating new records, or updating or deleting existing records.
List all customers
First, let's write a function that gets a list of all of our customers, including their name
, description
, Prismatic id
, and externalId
:
def getCustomers():
query = gql("""
query {
customers (isSystem: false) {
nodes {
id
name
description
externalId
}
}
}
""")
result = client.execute(query)
return result['customers']['nodes']
This function runs a customers query against Prismatic's API.
We specify isSystem: false
because your Prismatic tenant has a set of "system" customers that are used behind-the-scenes for running test integrations as you build them.
This will filter out those "system" customers, and return only your "real" customers.
The query returns an object with a customers
key, which has (in GraphQL lingo) has a series of nodes (customers).
We return result['customers']['nodes']
so that just a list of customer objects are returned.
Let's invoke this function, and use json.dumps()
for readability:
print(json.dumps(getCustomers()))
[
{
"id": "Q3VzdG9tZXI6MThjZTBjM2EtYmQ5NS00MWJiLWIyMjUtN2MwYjVjMDg3YmE4",
"name": "Mars Missions",
"description": "Mars Missions Corp",
"externalId": "abc-123"
},
{
"id": "Q3VzdG9tZXI6MDllZDQyNTctMTNkMS00YTY4LWFiNTktY2Y5NzNmZGUyOTQy",
"name": "Eastern Space Flight",
"description": "Eastern Space Flight - Houston, TX",
"externalId": "xyz-456"
}
]
We could use the output of this function to figure out which customers have not been synced into Prismatic, or to sync Prismatic customers back to an outside system.
GraphQL pagination
Before moving on, I want to address pagination. By default, the Prismatic API returns the first 100 results for a query. So, the customers query only returns 100 customers, even if we have more than 100 in the system.
If we want to download all customers, we'll need to update our getCustomers()
function a bit.
We'll update our query so that it requests pagination information (pageInfo
).
We'll request whether or not there are additional pages to fetch (hasNextPage
), and a unique ID indicating where to start the next page (endCursor
).
We'll feed that endCursor
back in to our query as a parameter after
, so the GraphQL API knows where it last left off:
def getCustomers():
query = gql("""
query ($startCursor: String){
customers(after: $startCursor, isSystem: false) {
nodes {
id
name
description
externalId
}
pageInfo {
hasNextPage
endCursor
}
}
}
""")
cursor = ""
hasNextPage = True
customers = [] # Used to accumulate customer objects
while hasNextPage:
result = client.execute(query, variable_values={"startCursor": cursor})
customers += result['customers']['nodes']
hasNextPage = result['customers']['pageInfo']['hasNextPage']
cursor = result['customers']['pageInfo']['endCursor']
return customers
Fetch a specific customer by external ID
Now, let's look at how to look up a specific customer by their externalId
.
The customers query allows you to filter customers by externalId
.
We will pass in the externalId
as a variable to the customers query using an object, params
, that contains the GraphQL variables we want to use.
We'll also assert that we got exactly one customer object back from the API:
def getCustomerByExternalId(externalId):
query = gql("""
query ($externalId: String!) {
customers (externalId: $externalId) {
nodes {
id
name
description
externalId
}
}
}
""")
params = {"externalId": externalId}
response = client.execute(query, variable_values=params)
assert len(response["customers"]["nodes"]) == 1, f"No customer with external ID '{externalId}' exists."
return response["customers"]["nodes"][0]
If we invoke this function now, we'll get a single customer based on the externalId
that we provide:
print(json.dumps(getCustomerByExternalId("abc-123")))
{
"id": "Q3VzdG9tZXI6MThjZTBjM2EtYmQ5NS00MWJiLWIyMjUtN2MwYjVjMDg3YmE4",
"name": "Mars Missions",
"description": "Mars Missions Corp",
"externalId": "abc-123"
}
Create a new customer
Next, we'll write a function that creates a new customer given the customer's name
, description
, and externalId
.
To do that, we'll use the the createCustomer GraphQL mutation.
Similar to the getCustomerByExternalId function above, we'll supply some input variables to our mutation with a params
object:
def createCustomer(name, description="", externalId=""):
mutation = gql("""
mutation($name: String!, $description: String, $externalId: String) {
createCustomer(
input: { name: $name, description: $description, externalId: $externalId }
) {
customer {
id
name
description
externalId
}
errors {
messages
}
}
}
""")
params = {
"name": name,
"description": description,
"externalId": externalId
}
result = client.execute(mutation, variable_values=params)
if ("errors" in result):
raise Exception(result.errors)
else:
return result["createCustomer"]["customer"]
The createCustomer
mutation will return errors
if the name
or externalId
you specified is already in the Prismatic system.
If the mutation does not throw an error, it will return a customer object containing the new customer's name
, description
, externalId
and generated Prismatic id
.
Let's try it out:
print(
json.dumps(
createCustomer(
name="Rockets Rockets Rockets",
description="Rockets^3",
externalId="456-xyz"
)))
{
"id": "Q3VzdG9tZXI6NGQ3ZDc3ZTktOTllNy00NmJiLWFlNDktMTg1N2JlNWNiYjUz",
"name": "Rockets Rockets Rockets",
"description": "Rockets^3",
"externalId": "456-xyz"
}
Delete a customer by External ID
For the last customer-related function, let's delete a customer given their external ID.
To do that, we'll use the deleteCustomer mutation.
First, we'll look up the customer we would like to delete using the getCustomerByExternalId function we wrote previously.
Then, we'll feed the Prismatic ID that we get into the deleteCustomer
mutation:
def deleteCustomer(externalId):
customer = getCustomerByExternalId(externalId)
mutation = gql("""
mutation ($id: ID!) {
deleteCustomer(
input: {
id: $id
}
) {
customer {
id
name
description
externalId
}
errors {
messages
}
}
}
""")
params = {"id": customer["id"]}
result = client.execute(mutation, variable_values=params)
if ("errors" in result):
raise Exception(result.errors)
else:
return result["deleteCustomer"]["customer"]
If we supply an external ID that is not attached to a customer, the getCustomerByExternalId()
function will throw an error indicating that.