Introduction
Welcome to the documentation for the MoyaApp Business Messaging API. If you run into any issues, or have questions or feedback, please let us know by sending a message to api-support@moya.app.
You can find extensive working sample code in Python in our GitHub repository. You can access many features of this API on our front end site.
Bot service integration
We provide a separate service to integrate directly with several common messaging platforms. If you wish to use this, please contact us to discuss options rather than trying to write your own integration.
The platforms we currently support are:
- RapidPro and TextIt. For setup details please see the RapidPro integration page
- AWS Lex. For a tutorial about how to set this up please see the Create an AWS Lex bot page
- Azure Bot Services. For setup details please see the Azure bot services integration page
- Botpress Only the open source version of Botpress is supported
- Voiceflow For setup details please see the Voiceflow integration page
Authentication
To access the Messaging API, you will need an access token. In order to obtain one, please send a message to api-support@moya.app specifying the mobile number that you would like to use.
The MoyaApp Messaging API uses Bearer tokens for authentication (please see RFC-6750 standard for more information). You can pass the token in one of two ways:
- In the request headers (e.g.
Authorization: Bearer YourSecretToken
) - Via URL parameter (e.g. by adding
?access_token=YourSecretToken
to the request URL).
Sending Messages
To send a message, simply send a POST request to /v1/message
as shown in the example.
This endpoint will send to any number that you specify, even if it is not on our system, or not active. For this reason we strongly recommend that you first filter for active Moya numbers using the API calls available.
Example Message:
Permission Required :SEND
HTTP Request
POST /v1/message
Example Request
curl --location --request POST 'https://api.moya.app/v1/message' \
--header 'Authorization: Bearer YourSecretToken' \
--header 'Content-Type: application/json' \
--data-raw '{
"to":"27123456789",
"recipient_type":"individual",
"type":"text",
"text": {
"body":"Hello World"
}
}'
Example Response
{
"id":"1ec019dc-c65f-6614-9d00-7f01b918938a"
}
JSON Parameters
Name | Type | Required | Description |
---|---|---|---|
to | String | Yes | This field is for the phone number of the user that you wish to message. |
recipient_type | String | Yes | Must be set to "individual" or "broadcast". See Broadcast Messages for more detail |
priority | String | No | See the Priority section below. Must be either "low", "medium", "high". Defaults to "medium" |
deduplication_id | String | No | See the Deduplication section below. Must be a valid UUID when set |
type | String | Yes | "text" or "image". See the below sections for the additional parameters required depending on the message type |
Text message parameters
Name | Type | Required | Description |
---|---|---|---|
text | JSON Object | Yes | Contains the body field below. |
text.body | String | Yes | The message body goes here. No more than 2048 characters. |
text.responses | JSON Object | No | Suggested quick responses. See Quick Responses section |
text.allow_merge | boolean | No | Default true . By default MoyaApp collapses messages that were sent in relative quick succession into one message bubble. Setting this flag to false disables this behaviour. |
Image message parameters
Name | Type | Required | Description |
---|---|---|---|
image | JSON Object | Yes | Contains the url field below. |
image.url | String | Yes | The URL previously retrieved through the upload_image endpoint |
Query Parameters
Name | Type | Required | Description |
---|---|---|---|
job_id | String | No | See the Jobs section below. Must be a valid UUID when set |
Result
The MoyaApp Messaging API will signal success with an HTTP Status code 200 and a JSON object with a parameter id
containing the id of the queued message.
Error Result
An error will be signaled with HTTP Status codes 4xx and 5xx and a JSON payload with more details. The JSON paloay is a complex object with the following parameters.
Example Error Response
{
"error": "MISSING_ARGUMENT",
"message": "Missing 'to' parameter"
}
Name | Type | Required | Description |
---|---|---|---|
error | Strinng | Yes | One of the following: MISSING_TOKEN, INVALID_TOKEN, INVALID_JSON, MISSING_ARGUMENT, INTERNAL_SERVER_ERROR, TOO_MANY_REQUESTS, SERVICE_UNAVAILABLE |
message | String | No | A detailed, human readable description of the error |
Formatting
You may want to add some formatting to your messages, for example bold or italic. The MoyaApp Android client fully implements the XEP-0393 spec to allow this. The MoyaApp iOS client implements some of the more commonly used features of this.
Formatting | Android | iOS | Example content | Example render |
---|---|---|---|---|
Bold | Yes | Yes | *text* |
text |
Italic | Yes | Yes | _text_ |
text |
Quoting | Yes | No | > text |
| text |
> > text |
| | text | |||
Strikethrough | Yes | No | ~text~ |
|
Monospace | Yes | No | `text` |
text |
Split message (into multiple message bubbles) |
Yes | No | Hi___---___How are you? |
Hi How are you? |
Note that the formatting characters stay visible to the user, until MoyaApp app version 4.5. If you want to send messages without visible formatting marks in older app versions, you can try using Unicode formatting, however only a subset of these codes will render correctly and display may be different between different OS and app versions.
A newline in the content creates a newline in the message. Use two newlines to create a new paragraph.
Emojis are fully supported and standard across all client versions on a given platform.
We advise that before sending out significant volumes of messages containing formatting you should test on multiple devices to ensure they are rendering as you wish, especially if you are intending to use Unicode formatting.
All formatting characters are included in the maximum size of a message (2048 bytes).
Web Links
Web links ie http://
and https://
reproduced verbatium, and are colored blue and clickable. They will open according to the standard rules of the host operating system (ie usually in a browser, but some can be captured by apps and redirected to them directly).
MoyaApp Links
We support various MoyaApp in-app links when a link begins with http://moya.me/
or moya://
.
Phone numbers
If the content is a phone number of the format +12345
then it will be rendered just as the phone number and upon click will open up a chat session within MoyaApp going to that number. For example https://moya.me/+12345
would render as +12345. Passing a text
query paramater allows you to pre-populate the chat message with that content. For example moya://+12345?text=A%20sample%20message
would render as +12345 and upon clicking a chat will be started with that number and the text box is prepopulated with "A sample message".
In-app links
Otherwise, if the content of the link is a recognised in-app MoyaApp link, for example to a Discover app or some internal part of MoyaApp, it will be displayed as the text of that link. For example moya://moya.payd
would render as MoyaPay where the link would open the MoyaPay section of the app.
If you have an app in Discover, our support team can advise you of the moyaKey
which it has been assigned which can be used to link directly to it from a chat message.
Quick Responses
You can add quick responses to text messages. Those appear as buttons to the user. To accomplish that simply add a responses
array inside the text
element. The responses
array contains an arbitrary number of response objects each consisting of the parameters response
, color
, background_color
. The color paramaters are optional.
curl --location --request POST 'https://api.moya.app/v1/message?job_id=59102c4f-084d-4566-b0ae-4fed6621cf14' \
--header 'Authorization: Bearer YourSecretToken' \
--header 'Content-Type: application/json' \
--data-raw '{
"to":"27123456789",
"recipient_type":"individual",
"type":"text",
"text": {
"body":"Are you interested in earning extra money with MoyaApp?đź’°",
"responses": [
{
"response": "Yebo",
"color": "#00ff00"
},
{
"response": "Not Really",
"background_color": "#ff0000",
"color": "#ffffff"
}
]
}
}'
Example Quick Response:
Response parameter
Name | Type | Required | Description |
---|---|---|---|
response | String | Yes | The suggested response |
color | String (hex color value) | No | The foreground (text) color of the button |
background_color | String (hex color value) | No | The background color of the button |
Request Location
Requesting a user's location can be done by including one of two uris
in
message body sent to the user. What the user sees will depend on the uri
and also the MoyaApp version
installed. An older version will display just the text on the message body and not the auto trigger button,
so it's advisable to be descriptive and clear on what you want the user to do
(as well as including any other information) in the message body before appending the uri, this will allow the user to
get more information (probably why you are asking for their location) as well as display the location share button
if able.
moya.location.request.gps
Including this in the message will prompt the user to respond with their current location if (location) enabledmoya.location.request
This will allow the user specify any location from an interactive map
Both responses will be received in the form of a geo URI. as described here
HTTP Request
POST /v1/message
Example Request
curl --location --request POST 'https://api.moya.app/v1/message' \
--header 'Authorization: Bearer YourSecretToken' \
--header 'Content-Type: application/json' \
--data-raw '{
"to":"27123456789",
"recipient_type":"individual",
"type":"text",
"text": {
"body":"Please share your location moya.location.request.gps"
}
}'
Request Date and Time
Supported from MoyaApp Android version 6.3.0
One can also request a user provides their date, time or datetime information by including one of the below uris
in the message body, and just like requesting location, the user sees a button (depending on their MoyaApp version) or
the uri itself together with any other information supplied. On click of the button, a calendar, time or both prompts
are displayed to allow the user send back the required information in the standard format specified below.
Name | URI | Response Format |
---|---|---|
Date And Time | moya.calendar.request |
yyyy-MM-dd'T'HH:mm:ss.SSS'Z' |
Date | moya.calendar.request.date |
yyyy-MM-dd |
Time | moya.calendar.request.time |
HH:mm |
Example Request
curl --location --request POST 'https://api.moya.app/v1/message' \
--header 'Authorization: Bearer YourSecretToken' \
--header 'Content-Type: application/json' \
--data-raw '{
"to":"27123456789",
"recipient_type":"individual",
"type":"text",
"text": {
"body":"Please share your current date and time moya.calendar.request"
}
}'
Deduplication
To handle timeouts on the HTTP request where the sender doesn’t know if the message was accepted or not the sender can optionally include a deduplication_id
in the request. When included the deduplication_id
must be a valid UUID otherwise the request will be rejected.
If the MoyaApp Messaging API notices a previously used tuple of recipient (to
) and deduplication_id
it will not send the message again but instead return the ID of the previously sent message.
Priority
Each sending account has an outgoing message queue. The message queue drains at a fixed rate. Messages in the queue are ordered by their priority and their time of submission. The default priority is medium
. If you always use the default priority you can put messages to the front of the queue by giving them a priority of high
.
If, for example, you are sending both conversational messages and mass messages at the same time and you want to ensure that conversational messages arrive as soon as possible you might want to tag your conversational messages as high
and all mass/broadcast messages with a priority of low
.
Jobs
You can batch groups of related messages by specifying a job_id
UUID query parameter (not a JSON parameter). For example if sending a mailshot to 1000 customers you could use the same job_id
. This can be specified for both message
and messages
endpoints.
The advantages of using a job_id
are:
- The ability to cancel jobs
- Reporting will give you details of per-batch
Permission Required :SEND
Example Request
curl --location --request POST 'https://api.moya.app/v1/message?job_id=59102c4f-084d-4566-b0ae-4fed6621cf14' \
--header 'Authorization: Bearer YourSecretToken' \
--header 'Content-Type: application/json' \
--data-raw '{
"to":"27123456789",
"recipient_type":"individual",
"type":"text",
"text": {
"body":"Hello World"
}
}'
Broadcast messages
The same message can be send to up to 10,000 recipients with one request. To send a message to more than one recipient set the recipient_type
attribute to broadcast
and use an array of multiple recipients as the to
attribute. This works for both text and image messages. If you want to send different messages to multiple recipients, please see the section below on Bulk Sending Messages.
Permission Required :SEND
Example Broadcast request
curl --location --request POST 'https://api.moya.app/v1/message' \
--header 'Authorization: Bearer YourSecretToken' \
--header 'Content-Type: application/json' \
--data-raw '{
"to":["27123456789","27222222111"],
"recipient_type":"broadcast",
"priority": "low",
"type":"text",
"text": {
"body":"Hello World"
}
}'
Example Broadcast response
{
"id": [
"1ec79d43-8ec0-6ac8-a30b-dbcc9e031abd",
"1ec79d43-8ec0-6ac9-a30b-dbcc9e031abd"
]
}
Sending Images
Sending images is a two-step process:
- The image is uploaded with the
upload_image
endpoint documented below. A #datafree optimized webp image URL is returned. - The image is sent with the
message
endpoint as documented. The #datafree optimized image URL from step (1) needs to be included in a message. The same uploaded image / URL can be used and send with multiple messages (See the next section).
Uploading an image
The first endpoint contains a file field to upload an image with a file extension of jpg, png, webp, gif, svg or bmp directly.
The endpoint then converts the image to an optimized image and returns a link to it. Please note that this link is only valid for 30 days.
Initially this link should be checked manually once to ensure that the optimized image displays correctly and all text, etc... is sufficiently legible before sending it out.
Example Image Send:
Permission Required :IMAGE_UPLOAD
HTTP Request
POST /v1/upload_image
Example 1: Uploads an image:
curl --location --request POST 'https://api.moya.app/v1/upload_image' \
--header 'Authorization: Bearer <YourSecretToken>' \
--form 'file=@"<ImageLocation>"' \
Example 1: Successful Response:
{
"url": "<DataFreeURL>/<YourBotNumber>/f9919ca9-5d57-49ae-ad91-329ffb23bdc9.webp"
}
Example 2: Uploads an image with public url and extension (either jpg, png, webp, gif, svg or bmp) :
curl --location --request POST 'https://api.moya.app/v1/upload_image' \
--header 'Authorization: Bearer <YourSecretToken>' \
--form 'url="<PublicURL>"' \
Example 2: Successful Response:
{
"url": "<DataFreeURL>/<YourBotNumber>/f9919ca9-5d57-49ae-ad91-329ffb23bdc9.webp"
}
Example 3: Error message, if file size is too big:
curl --location --request POST 'https://api.moya.app/v1/upload_image' \
--header 'Authorization: Bearer <YourSecretToken>' \
--form 'file=@"<ImageLocation>"' \
Example 3: Error response:
{
"errors": [
{
"status": 413,
"detail": "Maximum size is 1048576 bytes."
}
]
}
Form Parameters
Name | Type | Required | Description |
---|---|---|---|
file | File | file or url | Location to the file with a file extension of either jpg, png, webp, gif, svg or bmp. |
url | Text | file or url | Public file to url with file extension (currently only jpg, png, webp, gif, svg or bmp is allowed). |
Sending Files
Sending files #datafree is a two-step process:
- The file needs to be uploaded with the
upload_file
endpoint documented below. A #datafree URL will be returned. - The file is sent with the
message
endpoint as documented. The #datafree file URL from step (1) needs to be included in a message. The file URL can be used and send with multiple messages (See the next section).
Uploading a file
The first endpoint contains a file field to upload a file directly (preferred method) or using a url (requires an extension (Only .pdf is currently supported)).
This endpoint returns a link to the #datafree file. Please note that this link is only valid for 30 days.
Example File Message:
Permission Required :FILE_UPLOAD
HTTP Request
POST /v1/upload_file
Example 1: Uploading a file (preferred method):
curl --location --request POST 'https://api.moya.app/v1/upload_file' \
--header 'Authorization: Bearer <YourSecretToken>' \
--form 'file=@"<FileLocation>"' \
Example 1: Successful Response:
{
"url": "<DataFreeURL>/<YourBotNumber>/f9919ca9-5d57-49ae-ad91-329ffb23bdc9.pdf"
}
Example 2: Uploading a file with an url:
curl --location --request POST 'https://api.moya.app/v1/upload_file' \
--header 'Authorization: Bearer <YourSecretToken>' \
--form 'url="<PublicURL>"' \
Example 2: Successful Response:
{
"url": "<DataFreeURL>/<YourBotNumber>/f9919ca9-5d57-49ae-ad91-329ffb23bdc9.pdf"
}
Example 3: Error message, if file size is too big:
curl --location --request POST 'https://api.moya.app/v1/upload_file' \
--header 'Authorization: Bearer <YourSecretToken>' \
--form 'file=@"<ImageLocation>"' \
Example 3: Error response:
{
"errors": [
{
"status": 413,
"detail": "Maximum size is 1048576 bytes."
}
]
}
Form Parameters
File Parameters
Name | Type | Required | Description |
---|---|---|---|
file | File | file or url | Location to the file. |
url | Text | file or url | Public file to url with file extension (currently only .pdf is allowed). |
Sending an image or file to a user
To send an image/file message, simply send a POST request to /v1/message
as shown in the example. Full details of this endpoint can be found in its documentation above. All parameters for text messaging are valid with image/file #datafree attachment.
A type parameter is required: type
:image
/file
specifying whether it's an image or a file.
Next an image/file parameter should be added with a dictionary and an url
parameter with the link to the image/file (supply the #datafree url generated from the image_upload/file_upload endpoint). See the example for more information.
Permission Required :SEND
HTTP Request
POST /v1/message
Example 1 Request
curl --location --request POST 'https://api.moya.app/v1/message' \
--header 'Authorization: Bearer YourSecretToken' \
--header 'Content-Type: application/json' \
--data-raw '{
"to":"27123456789",
"recipient_type":"individual",
"type":"image",
"image": {
"url":"<DataFreeURL>/<YourBotNumber>/f9919ca9-5d57-49ae-ad91-329ffb23bdc9.webp"
}
}'
Example 1 Response
{
"id":"1ec019dc-c65f-6714-9d00-7f01b918938a"
}
Example 2 Request
curl --location --request POST 'https://api.moya.app/v1/message' \
--header 'Authorization: Bearer YourSecretToken' \
--header 'Content-Type: application/json' \
--data-raw '{
"to":"27123456789",
"recipient_type":"individual",
"type":"file",
"file": {
"url":"<DataFreeURL>/<YourBotNumber>/f9919ca9-5d57-49ae-ad91-329ffb23bdc9.pdf"
}
}'
Example 2 Response
{
"id":"1ec019dc-c65f-6614-9d00-7f01b918938a"
}
Cancelling In-progress Sending
If you specified a job_id
query parameter for your send process, you are able to cancel any which have been queued but not yet sent.
HTTP Request
DELETE /v1/jobs/<job_id>
Example Request
curl --request DELETE --location 'https://api.moya.app/v1/jobs/c1592dbe-ead6-4af3-8f01-0154480379ac' \
--header 'Authorization: Bearer YourSecretToken' \
Result
The MoyaApp Messaging API will signal success with an HTTP Status code 200 and an empty JSON object ({}
).
Bulk Sending Messages
You can send multiple messages (text or image) in a single HTTP request using the messages
endpoint
Permission Required :SEND
HTTP Request
POST /v1/messages
Example Request
curl --location --request POST 'https://api.moya.app/v1/messages' \
--header 'Authorization: Bearer YourSecretToken' \
--header 'Content-Type: application/json' \
--data-raw '[
{
"to":"27123456789",
"recipient_type":"individual",
"type":"text",
"text": {
"body":"Hello World"
}
},
{
"to":"27123456790",
"recipient_type":"individual",
"type":"text",
"text": {
"body":"Other message"
}
}
]'
Example Response
[
{
"id":"1ec019dc-c65f-6614-9d00-7f01b918938a"
},
{
"id":"1ec019dc-c65f-6614-9d00-7f01b918939c"
}
]
Parameters
Simply pass an array of objects one per message as specified in the Sending Messages section above. The response is a list of responses in the same order as the messages that were passed.
Additionally you can also pass a query parameter of job_id
which is applied
to all messages sent by the request.
As with broadcast messages, there is a hard limit of sending to 10,000 recipients per HTTP request. Additionally the total size of the JSON body cannot exceed 10Mb.
Account details
Configuration
The current configuration of an account can be checked via the /v1/configuration
endpoint. Simply send an authenticated GET request.
curl 'https://api.moya.app/v1/configuration' \
--header 'Authorization: Bearer YourSecretToken'
Example Account Details:
Example Response
{
"configuration": {
"phone_number": "2700008008",
"name": "Lesedi from MoyaApp",
"rate": 2,
"event_url": "http://localhost:1234/",
"events": [
"MESSAGE",
"STATUS"
]
}
}
Modification
Specific account information can be modified at any time using /v1/update
endpoint.
Fields allowed to be modified include name
, allowed_events
, event_url
and status_url
.
All data fields are optional.
Request json data should contain the required value against the field to be modified, any omitted or null value field will be left unmodified.
name
: Renaming the botallowed_events
: Allowed notification events, available options include- MESSAGE: Notifications for bot's message events will be sent to the event url
- STATUS: Bot's message state, i.e. delivered, failed, etc will be sent to the status url
event_url
: Url for events notificationsstatus_url
: Url for status notifications
Values entered for event_url
and status_url
should adhere to RFC 6570 URI Template Requirements.
Variables may include
to
: Phone number setup against the current account.
If an account's phone number is 27123456789
, then
http://example.com/v1/webhook/{to}
will translate into http://example.com/v1/webhook/27123456789
Permission Required :MODIFY
curl --location --request PUT 'https://api.moya.app/v1/update' \
--header 'Authorization: Bearer YourSecretToken' \
--header 'Content-Type: application/json' \
--data-raw '{
"name":"newBotName",
"event_url": "http://example.com/v1/webhook/{to}",
"allowed_events": [
"MESSAGE","STATUS"
]
}'
Avatar image
The account's currently configured avatar can be requested via the /v1/avatar
endpoint.
If you do not currently have an avatar set, a HTTP 404 error will be returned. If you do have an avatar, the response will be an image (currently png format).
Creating/Modifying avatar image
An avatar can also be created or replaced by sending a PUT
request through the /v1/avatar
endpoint with the relevant parameters. If you want to manually change your profile image you can do this via the manage tokens page in the business messaging API frontend
Permission Required :MODIFY
curl --location --request PUT 'https://api.moya.app/v1/avatar' --header 'Content-Type: multipart/form-data'
--header 'Authorization: Bearer YourSecretToken' --form 'file=@/path/to/image/file'
Health
Use the health
node to check the status of your MoyaApp Messaging API account.
Permission Required :HEALTH
HTTP Request
GET /v1/health
Example Request
curl --location 'https://api.moya.app/v1/health' \
--header 'Authorization: Bearer YourSecretToken' \
Example Response
{
"health": {
"gateway_status": "connected",
"ping": {
"success": true,
"time": 23
},
"delay": {
"delay": 0,
"priority": "low"
},
"pending_events": 0
}
}
Result Parameter
Name | Type | Description |
---|---|---|
health | JSON Object | A complex object containing various health parameter |
health.gateway_status | String | 'disconnected', 'connecting', 'connected' indicates if the account is connected to the network |
health.ping.success | Boolean | Is MoyaApp network reachable |
health.ping.time | Integer | Round trip time in miliseconds to MoyaApp server |
health.delay.delay | Double | Time the most recently sent message spent in the queue in seconds |
health.delay.priority | String | Priority of the most recently sent message |
health.pending_events | Integer | Number of events currently queued for delivery to the webhook |
Token
Token information can also be fetched. This will give a response containing the jid
and activities
linked with this token. Activities listed below, represent the available operations allowed for this token
SEND
: Allows the bot to send messagesMODIFY
: Modification of Bot's informationHEALTH
: Ability to check Bot's healthIMAGE_UPLOAD
: Allows Avatar uploadFILE_UPLOAD
: Allows file uploadUSER_LOOKUP
: Allows user lookupSTATS
: View stats about send jobsKYC
: Request KYC from user
HTTP Request
GET /v1/token/info
Example Request
curl --location 'https://api.moya.app/v1/token/info' \
--header 'Authorization: Bearer YourSecretToken' \
Example Response
{
"jid": "27123456789@binu.m.in-app.io",
"expiry": "2022-09-28T09:17:44",
"activities": [
"USER_LOOKUP",
"SEND",
"MODIFY"
],
"disabled": false,
}
Result Parameter
Name | Type | Description |
---|---|---|
jid | String | Bot's jid |
expiry | DateTime | Token expiry date. A null/unspecified value means token will never expire |
activities | Array | List of allowed endpoint operations for this token |
disabled | Boolean | Token enabled flag |
User lookups
Bulk contact lookups
To check if a list of user phone numbers from your database correspond to valid MoyaApp accounts, please submit a POST request as shown in the examples below. An account is considered valid if the user has been active in the past 90 days.
The /v1/contacts endpoint supports 2 formats in which a list of numbers can be submitted, plaintext and hashed (for added security in transmitting a list of your contacts over the Internet). Please use the hashed parameter (bool), to indicate whether you are submitting a list of hashed or plain numbers.
Example Request 1 with plaintext numbers
curl --location --request POST 'https://api.moya.app/v1/contacts' \
--header 'Authorization: Bearer YourSecretToken' \
--header 'Content-Type: application/json' \
--data-raw '{
"contacts": [
"27123456789",
"27000000000",
"27987654321"
]
}'
Example Response (1 of the submitted numbers identified as a MoyaApp user and returned)
{
"error": false,
"match_count": 1,
"match_list": ["27123456789"]
}
Example Request with hashed numbers
curl --location --request POST 'https://api.moya.app/v1/contacts?last_seen_days_ago=50' \
--header 'Authorization: Bearer YourSecretToken' \
--header 'Content-Type: application/json' \
--data-raw '{
"contacts": [
"FD9F304A987B7354A6AF8BF25ECD63DD32F8D7349F6473B890F0320613D04470",
"555CDE815EF46CC7F2C1B0EAFD78F04E1293061FCC176E6A8202AE1C3A47080F",
],
"hashed":true
}'
Example Response (1 of the submitted numbers identified as a MoyaApp user and returned)
{
"error": false,
"match_count": 1,
"match_list": ["FD9F304A987B7354A6AF8BF25ECD63DD32F8D7349F6473B890F0320613D04470"]
}
Permission Required :USER_LOOKUP
HTTP Request
POST https://api.moya.app/v1/contacts
Parameters
Name | Type | Required | Description |
---|---|---|---|
contacts | Array | Yes | Array of phone numbers (up to 1000 at a time) that you are validating. The numbers must be in international format, excluding trailing zeros and plus sign (e.g.: 27123456789). When using hashed parameter, the numbers should be hashed first. If you provide plaintext numbers with this parameter on, lookup will fail. |
hashed | Boolean | Yes | Set this to true if the numbers are being transmitted in hashed format. Defaults to false. |
You can additionally specify the following query parameters:
Name | Type | Default | Description |
---|---|---|---|
last_seen_days_ago |
integer | 90 | Ensure user was active within this time period. The minimum value is 7 days. |
Here is a diagram representing an example process flow to check for user base overlap, in more detail.
Individual user lookups
You can call this endpoint to find specific details about a user who was active within the last 90 days. You must pass either a phone number or a "MoyaApp Device ID" (also known as a DID) in order to look up this information.
A MoyaApp DID is a UUID which uniquely identifies a user's device install and
is a way of securely passing a user between systems without explicitly sending
their phone number. You can use this call to take a MoyaApp DID and map it to the
users phone number. If the user changes devices or for many other reasons, a
DID will reset so these are not intended to be stored for long periods of time
but rather used immeditately to look up the user's phone number. For apps in
MoyaApp Discover, a DID is automatically added to the query parameters of the
url (if the Discover item has been configured with "useDid": true
). This is
passed as the x-binu-did
query parameter.
You can additionally specify the following query parameters:
Name | Type | Default | Description |
---|---|---|---|
last_seen_days_ago |
integer | 90 | Ensure user was active within this time period. The minimum value is 7 days. |
Example 1: Request with a number:
curl -s --header "Authorization: Bearer YourSecretToken" https://api.moya.app/v1/users/27123456789?last_seen_days_ago=50
Example 1: Successful Response:
{
"user_profile": {
"number": "27123456789",
"jid": "27123456789@binu.m.in-app.io",
"aid": "64afb426-6062-486a-ab89-62e55a780a4e",
"did": "84eaad4b-d2a4-44f0-8de1-49b11ec6c084",
"last_seen": "2021-11-15 13:46:31.439036",
"registration_date": "2020-06-04 19:22:19.141956"
},
"error": false
}
Example 2: Request with a did (device identifier):
curl -s --header "Authorization: Bearer YourSecretToken" https://api.moya.app/v1/users/84eaad4b-d2a4-44f0-8de1-49b11ec6c084
Example 2: Successful Response:
{
"user_profile": {
"number": "27123456789",
"jid": "27123456789@binu.m.in-app.io",
"aid": "64afb426-6062-486a-ab89-62e55a780a4e",
"did": "84eaad4b-d2a4-44f0-8de1-49b11ec6c084",
"last_seen": "2021-11-15 13:46:31.439036",
"registration_date": "2020-06-04 19:22:19.141956"
},
"error": false
}
Example 3: Error message, not found/last_seen more than 90 days:
curl -s --header "Authorization: Bearer YourSecretToken" https://api.moya.app/v1/users/2700000000
Example 3: Error response:
{
"errors": [
{"status": 404, "detail": "No such user 27000000000."}
]
}
Permission Required :USER_LOOKUP
HTTP Request
GET /v1/users/<reference>
The reference parameter could be either a number or a MoyaApp DID
Parameters
Name | Type | Required | Field | Description |
---|---|---|---|---|
reference | String | Yes (either number or DID) | number | This field is for the phone number of the requested user. |
- | - | - | did | This field is for the DID (MoyaApp Device Identifier) of the requested user. |
Webhooks
Webhooks are are HTTP callbacks that trigger on specific events. To receive events via your MoyaApp API integration, you need to have configured a URL "inbox" where the events can be sent to (e.g. https://your.webservice.com/secret/moya-event-endpoint
). You can configure this URL using the messaging API frontend manage tokens page and clicking the "edit configuration settings" option for the bot.
Incoming events are JSON payloads that will be sent via HTTP POST. Events have a top level field indicating the type of event. Available fields are:
Name | Description |
---|---|
message | Used to notify about a new message and the content of that message |
status | Used to notify about status changes in a message you sent |
To acknowledge the receipt of an event, please ensure your service responds with HTTP code 200 (OK) within 30 seconds. An error or timeout will show a delivery failure message to the user.
To reduce load on your webhook you don’t need to subscribe to all events. For example if you are not going to process status events you can subscribe to message events only. This is configured via the messaging API frontend manage tokens page.
Message
The message
field contains an object with the following attributes:
Attributes
Name | Type | Description |
---|---|---|
type | String | The type of the message. One of: text |
from | String | The phone number of the user that sent the message |
text | JSON Object | Only set when message is of type text . |
text.body | String | The message body of a text message |
Incoming messages will be sent via HTTP POST in the format below:
{
"message":
{
"type":"text",
"from":"27123456789",
"text":
{
"body":"Hello"
}
}
}
Receiving location
When a user sends their location, using the 'Attachments / Send location' button in the app, it will be sent as a message event of type text with the body being set to a geo URI.
{
"message":
{
"type":"text",
"from":"27123456789",
"text":
{
"body":"geo:48.7757715,6.07773784;u=12"
}
}
}
If the user hand picked their location, or if a default location has been used due to lack of Location permission, the uncertainty (u) parameter will be omitted from the URI. If the location came directly from GPS the uncertainty parameter will contain the precision in meters. (Lower values are better.). For example geo:48.7757715,6.07773784;u=12
geo:-30.559482,22.937506
is the default location for users in South Africa who didn’t provide location permission and didn’t manually move the position around.
Receive image
When a user sends an image, the webhook body will contain a link to an encrypted version of the image. The image can be decrypted using only the url using the following script: Image Decryptor
Script usage example:
python decrypt-picture.py "aesgcm://..." --output="test.webp"
The script requires the following dependencies:
cryptography==43.0.1
requests==2.32.3
This script can also be modified and used in backend systems. It uses standard encryption algorithms, and can be translated to most languages and frameworks.
Receiving sensitive messages
Sometimes users might send sensitive messages, for example after being prompted for a password, that should not remain part of the on-device chat history. In that case the recipient has the abililty to delete that message from the user’s device. Determination of what constitutes a sensitive messages falls to the recipient.
Response code
The easiest method to achieve this is to respond with HTTP code 205 (Reset Content) on the message web hook.
URL HTTP Request
POST /v1/received-sensitive
JSON Parameters
Name | Type | Required | Description |
---|---|---|---|
from | String | Yes | The phone number of the message the sensitive content was received from. Copied from the from attribute of the message event. |
id | String | Yes | The id of the sensitive message. Copied from the id attribute of the message event. |
Status
The status
field contains an object with the following parameters:
Incoming message status update will be sent via HTTP POST in the format below:
{
"status": {
"id": "1ec019dc-c65f-6614-9d00-7f01b918938a",
"recipient_id": "27123456789",
"status": "sent"
}
}
Parameters
Name | Type | Description |
---|---|---|
id | String | A UUIDv6. Matches the id returned upon sending a message |
recipient_id | String | The phone number the message was sent to |
status | String | One of the following: sent , delivered_to_user , read , delivery_failed |
Status field meanings
Name | Meaning |
---|---|
sent |
Message has been sent from our internal queue to the server |
delivered_to_user |
Message has arrived on the users' phone |
read |
Message has been read by the user (may not be triggered if for example the user has read-receipts turned off) |
delivery_failed |
There was an error, for example the user did not exist on our system |
User Data Requests
User data requests are used to request user information from MoyaApp.
Before sending user data requests please request access to user data requests from api-support@moya.app
Permission Required: KYC
Start
User Data requests are started by sending the user number to the user_data/start/
endpoint with the requested data fields in the body:
POST /v1/user_data/start/<USER NUMBER>
Json body:
{
"reason": "Demo app would like access to your information",
"data": [
{
"property": "first_name",
"reason": "display on your profile",
"required": true
},
{
"property": "last_name",
"reason": "display on your profile",
"required": false
}
]
}
With a successful request a "request Id" is generated and returned. Please see the example below next.
Example 1: Successful Request
curl --location --request POST 'https://api.moya.app/v1/user_data/start/<USER NUMBER>' \
--header 'Authorization: Bearer YourSecretToken' \
--header 'Content-Type: application/json' \
--data-raw '{
"reason": "Demo app would like access to your information",
"data": [
{
"property": "first_name",
"reason": "Your name to display on our profile",
"required": false
},
{
"property": "last_name",
"reason": "Your last name to display on our profile",
"required": false
}
]
}'
Example 1: Successful Response (Status : 202)
{
"requestId": "11111111-1111-1111-1111-111111111111"
}
Parameters
Name | Type | Description |
---|---|---|
reason | String (Optional) | A reason shown to the user for the data request. |
data.property | String | A property name (see available properties list below). |
data.reason | String (Optional) | A reason for the specific property shown to the user. |
data.required | Bool | Whether the property should be shown as required (greyed out checkbox, user accepts property). |
A required property (true value) will be shown to the user with a greyed out checkbox. If the user "Accept" the user data request, required properties will be approved. If a required property is false then the user need to select the field (checked checkbox) to approve a field.
Properties list
Certain properties can be fetched without needing permission from the user. If you are only fetching these, consider using the one-shot fetch mechanism documented below:
Name | Example | Description |
---|---|---|
number |
27821231234 | Phone number of the user, validated by SMS OTP |
last_seen |
2022-12-31 | The date when the user was last seen on our platform |
first_seen |
2022-05-12 | The date when the user was first seen on our platform |
did |
79228d8f-38e4-47fc-b673-156f12903de6 | The device ID |
device_platform |
android | The OS that the user is using |
app_version |
5.4.3 | The MoyaApp version the user is on |
Most properties require the user to accept the KYC request:
Name | Example | Description |
---|---|---|
first_name |
Bill | Name of user. |
last_name |
Smith | Last name of user. |
date_of_birth |
1999-01-01 | Date of birth of user in ISO format. |
device_make |
samsung | The user's device make |
device_model |
SM-A515F | The user's device model code |
device_tac |
35168418 | The user's device TAC |
device_os |
12 | The user's device OS version |
display_name |
BSmith | The user's profile name on Moya |
id_number |
9901011011080 | ID Number/Passport of user. |
verified_id_number |
9901011011080 | ID Number/Passport of user having been verified by our system. (Note due to additional cost needs a token with the kyc:fetch:verified_id scope). |
id_issue_date |
2020-12-31 | Date when the ID card/passport was issued |
id_expiry_date |
2032-12-31 | Date when the ID card/passport expires |
citizenship |
ZA | ISO country code of citizenship on ID card |
moyapay_document_type |
id | id/passport |
moyapay_document_country |
SOUTH_AFRICA |
Country that issued the document |
gender |
Male | User's gender |
children |
4 | The number of children that the user has |
email |
mark@gmail.com | The users' email address |
ethnicity |
colored | The users ethnicity |
work_status |
Working | Details about the users' job |
marital_status |
Living with a partner | Details about the users marital status |
monthly_income |
R3000-R10k | Income bracket |
education_level |
Lower secondary school usually from 11 - 15 years old | User's education level |
suburb |
eThekwini Ward 30 | The suburb that the user is in |
municipality |
eThekwini Metropolitan Municipality | The municipality that the user is in |
city |
Johannesburg | The city that the user is in |
province |
gauteng | The region that the user is in |
postcode |
0123 | The users postcode |
location_accuracy |
10 | The rough accuracy of the location data in kilometers, (0 = very accurate) |
Fetch
User Data requests are fetched by sending the request id to the user_data/fetch/
endpoint. Results will only be available if the user accepts the user request in MoyaApp.
An optional query parameter named long_poll
can be specified if you wish to
wait a certain amount of time (up to 60 seconds) if you want the call to hang
until the user has responded.
See below for the status list.
GET /v1/user_data/fetch/<REQUEST ID>?long_poll=30
Example Response - Pending (200)
{
"status": "pending"
}
Example Response - Completed all fields approved (200)
{
"status": "completed",
"results": {
"first_name": {
"source": "moyapay",
"value": "NAME OF USER",
"timestamp": "2022-12-16T16:28:42",
"status": "approved"
},
"last_name": {
"source": "moyapay",
"value": "SURNAME OF USER",
"timestamp": "2022-12-16T16:28:42",
"status": "approved"
}
}
}
Example Response - Completed, one field rejected (200)
{
"status": "completed",
"results": {
"first_name": {
"status": "rejected"
},
"last_name": {
"source": "moyapay",
"value": "SURNAME OF USER",
"timestamp": "2022-12-16T16:28:42",
"status": "approved"
}
}
}
Curl example:
curl 'https://api.moya.app/v1/user_data/fetch/<REQUEST ID>' \
--header 'Authorization: Bearer YourSecretToken'
Parameters
Name | Type | Description |
---|---|---|
Request ID | String | The request ID generated from the start endpoint |
Status list
Name | Description |
---|---|
completed |
User data request was completed by the user. |
pending |
User data request is still pending ( waiting for user to respond). |
expired |
The request has expired and you will need to issue a new one. |
approved |
User approved user data request for field. |
rejected |
User rejected user data request for field. |
not_found |
Request ID or field not found. |
invalid_format |
Request ID invalid format. |
Sources
The source
parameter says which database in Moya this data came from. We may
have different sources of data and these are tried in order of reliability. For
example, for date_of_birth
we may have a verified date of birth from a
MoyaPay application, or something given when signing up to Moya Research, or
something which was given when user first signed up to Moya.
Name | Reliability | Description |
---|---|---|
moyapay |
High (validated against ID card) | Given when the user applied to MoyaPay and was verified. This is based data that was on the RSA-ID or Passport photo provided in the application process |
verified_ids |
High (validated against ID card) | The ID number belongs to the specified user as validated by department of home affairs data. |
moya-stats |
High | Reported by the user's device on a daily basis |
moya |
High | Moya meta-data during signup process (eg phone number) |
gps |
Medium | Sent via the phone's GPS |
ip |
Medium | Geolookup based on the user's IP address when interacting with services |
loadshedding |
Medium | Sent from the loadshedding app |
research |
Medium | Self-reported when user joined the Moya Research panel |
proof-of-address |
Medium | Self-reported when user requested proof of address |
leadgen |
Medium | Self-reported when user filled in a leadgen form |
moyapay-unverified |
Medium | Given when the user applied to MoyaPay, but unlike moyapay has not been verified against government databases. |
onboard |
Low | Self-reported when user first joined Moya |
Permission Required: KYC
One-shot fetch
If you are fetching user properties which are automatically granted then you can combine the /start and /fetch calls into a single call. The input parameters are the same as the /start, and the response is the same as /fetch:
POST /v1/user_data/fetch/<USER NUMBER>
JSON body:
{
"data": [
{
"property": "app_version",
"required": true
}
]
}
Example Response - Completed (200)
{
"status": "completed",
"results": {
"app_version": {
"source": "moya-stats",
"value": "6.1.2",
"timestamp": "2023-12-16T16:28:42",
"status": "approved"
}
}
}
Check
You can check if a given set of data points exists for a user without requiring the users consent using this endpoint.
POST /v1/user_data/check/<USER NUMBER>
Json body:
{
"data": [
{ "property": "first_name" },
{ "property": "last_name" }
]
}
Return data looks very similar to the fetch endpoint but does not include the source
or value
fields.
Example 1: Successful Request
curl --location --request POST 'https://api.moya.app/v1/user_data/start/<USER NUMBER>' \
--header 'Authorization: Bearer YourSecretToken' \
--header 'Content-Type: application/json' \
--data-raw '{
"data": [
{ "property": "first_name" },
{ "property": "last_name" }
]
}'
Example 1: Successful Response (Status: 200)
{
"requestId": "11111111-1111-1111-1111-111111111111",
"results": {
"first_name": {"timestamp": "2023-09-23T09:55:29", "status": "found"},
"last_name": {"timestamp": "2023-09-23T09:55:29", "status": "found"}
}
}